The id-arena crate describes a simple example of using an arena to manage memory allocation in scenarios where you're okay with everything having a shared lifetime:
use id_arena::{Arena, Id};
type AstNodeId = Id<AstNode>;
#[derive(Debug, Eq, PartialEq)]
pub enum AstNode {
Const(i64),
Var(String),
Add {
lhs: AstNodeId,
rhs: AstNodeId,
},
Sub {
lhs: AstNodeId,
rhs: AstNodeId,
},
Mul {
lhs: AstNodeId,
rhs: AstNodeId,
},
Div {
lhs: AstNodeId,
rhs: AstNodeId,
},
}
let mut ast_nodes = Arena::<AstNode>::new();
// Create the AST for `a * (b + 3)`.
let three = ast_nodes.alloc(AstNode::Const(3));
let b = ast_nodes.alloc(AstNode::Var("b".into()));
let b_plus_three = ast_nodes.alloc(AstNode::Add {
lhs: b,
rhs: three,
});
let a = ast_nodes.alloc(AstNode::Var("a".into()));
let a_times_b_plus_three = ast_nodes.alloc(AstNode::Mul {
lhs: a,
rhs: b_plus_three,
});
// Can use indexing to access allocated nodes.
assert_eq!(ast_nodes[three], AstNode::Const(3));
This seems better for my use case than cloning structures each time they are reused. But, this approach makes it less clear how to implement basic traits on my data structures that use the returned Ids:
impl std::fmt::Display for AstNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AstNode::Const(x) => write!(f, "{}", x),
AstNode::Var(x) => write!(f, "{}", x),
AstNode::Add { lhs, rhs } => // ??? how to obtain a ref to the original AstNode here?
}
}
}
The same issue arrises for other traits I'd like to implement like PartialEq as well.
More generally, what I'd love to do is come up with some implementation of "From" that lets me do something like this:
struct AstNodeId(Id<AstNode>);
impl From<AstNodeId> for &AstNode {
fn from(t: TypeRef) -> Self {
// ???
}
}
... so that I can continue writing my code as if AstNodeId is just a Box or pointer to somewhere. But I'm not sure how I would implement this. With id-arena, I can't seem to create Arena::new() as a top-level static or const value so, global variables seem out of the question. Is there some other design pattern / solution that will allow me to do this?
Also, could interning be a solution here / used instead? When should I use interning vs arenas in Rust?
Related
I'm using AsRef<T> and AsMut<T> to expose an wrapped value in an enum. Can anyone tell me if this is an anti-pattern? I came across Is it considered a bad practice to implement Deref for newtypes?, and it convinced me that Deref would be a bad idea, but I'm not sure about the approach below.
pub enum Node {
Stmt(Statement),
Expr(Expression),
}
impl AsMut<Expression> for Node {
fn as_mut(&mut self) -> &mut Expression {
match self {
Node::Stmt(_) => panic!("fatal: expected Expression"),
Node::Expr(e) => e,
}
}
}
impl AsMut<Expression> for Box<Node> {
fn as_mut(&mut self) -> &mut Expression {
(**self).as_mut()
}
}
impl AsMut<Expression> for Expression {
fn as_mut(&mut self) -> &mut Expression {
self
}
}
fn check_binop<T: AsMut<Expression>>(
&mut self,
sym: Symbol,
lhs: &mut T,
rhs: &mut T,
ty: &mut Option<Type>,
) -> Result<Type, String> {
let lhs = lhs.as_mut();
let rhs = rhs.as_mut();
...
}
I'm considering just making my own traits (AsExpr<T> and AsExprMut<T>) that are just re-implementations of AsRef<T> and AsMut<T>. Functionally no different, but I think it would be clearer.
I strongly suggest you do not do this, especially considering the docs for AsRef and AsMut say in bold:
Note: This trait must not fail.
and I would definitely consider panicking to be failure. This isn't an anti-pattern so much as it is breaking the contract you opt-into by implementing those traits. You should consider returning an Option<&mut Expression> instead of going through AsRef/AsMut, or making your own traits like you mentioned.
I'm new to rust and I'm getting an error which I wasn't able to solve on my own.
I was advised to use a Cow but the person then said it wasn't possible after further inspection.
Link to playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d2910c05fed1ed8c615c28eaddf77d1d
Code:
use std::collections::HashMap;
enum Value<'a> {
Symbol(&'a str),
Call(&'a str, Vec<&'a Value<'a>>),
}
enum V<'a> {
Null,
String(&'a str),
Symbol(&'a str),
Builtin(Box<dyn for<'b> Fn(Vec<&'b V>) -> V<'b>>),
Function(Vec<&'a str>, &'a ()),
}
struct S<'a> {
i: HashMap<&'a str, V<'a>>,
parent: Option<&'a S<'a>>,
}
impl<'a> S<'a> {
fn lookup(&self, name: &str) -> &V<'a> {
match self.i.get(name) {
Some(v) => v,
None => match self.parent {
Some(parent) => parent.lookup(name),
None => &V::Null,
},
}
}
fn put(&mut self, name: &'a str, v: V<'a>) {
self.i.entry(name).or_insert(v);
}
}
fn eval<'a>(val: &'a Value, s: &'a S<'a>) -> V<'a> {
match val {
Value::Symbol(str) => V::Symbol(str),
Value::Call(str, args) => match s.lookup(str) {
V::Builtin(_fn) => {
let args: Vec<&V> = args
.iter()
.map(|z| {
let s0 = eval(z, s);
match s0 {
V::Symbol(str) => s.lookup(str),
_ => &s0
}
})
.collect();
_fn(args)
}
_ => V::Null,
},
}
}
fn main() {
let mut m = S {
i: HashMap::new(),
parent: None,
};
m.put("x", V::Null);
m.put("y", V::Builtin(Box::new(|_x| V::Null)));
let v = Value::Call("y", vec![&Value::Symbol("x")]);
eval(&v, &m);
}
And the error:
error[E0515]: cannot return reference to local variable `s0`
--> src/main.rs:47:34
|
47 | ... _ => &s0
| ^^^ returns a reference to data owned by the current function
Your eval function is creating data and trying to return it. You probably want to return it by value, without the reference.
_ => s0
If you don't own the data, then you'll need to make a clone of it. Your type makes extensive use of Vec, so it'll never be able to implement Copy. It's pretty close to implementing clone, though. The only issue is
Builtin(Box<dyn for<'b> Fn(Vec<&'b V>) -> V<'b>>),
There's no general way to clone a dyn Fn. If you really need general cloning on something like that, you can look into reference-counted types like Rc and Arc, but that's probably massively overkill for this.
Assuming Builtin refers to a handful of language builtins that are set at compile-time, they're probably all top-level Rust functions. The type Fn encompasses not just ordinary functions but also closures (and, I believe in new versions of Rust, any user-defined class can implement that trait as well by hand). If all you need is ordinary closures, use fn (note the lowercase "f"). This will only allow ordinary Rust function pointers, not closures or anything more advanced.
Builtin(fn(Vec<&'b V>) -> V<'b>),
The nice thing about fn (as opposed to dyn Fn) is that it's Clone, so we can #[derive(Clone)] now.
I am modeling an API where method overloading would be a good fit. My naïve attempt failed:
// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`
I then added an enum and worked through to:
enum IntOrFloat {
Int(i32),
Float(f32),
}
fn attempt_2(_x: IntOrFloat) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
// Can't pass the value directly
// attempt_2(i);
// attempt_2(f);
// Error: mismatched types: expected enum `IntOrFloat`
attempt_2(IntOrFloat::Int(i));
attempt_2(IntOrFloat::Float(f));
// Ugly that the caller has to explicitly wrap the parameter
}
Doing some quick searches, I've found some references that talk about overloading, and all of them seem to end in "we aren't going to allow this, but give traits a try". So I tried:
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_3(_x: &dyn IntOrFloatTrait) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_3(&i);
attempt_3(&f);
// Better, but the caller still has to explicitly take the reference
}
Is this the closest I can get to method overloading? Is there a cleaner way?
Yes, there is, and you almost got it already. Traits are the way to go, but you don't need trait objects, use generics:
#[derive(Debug)]
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_4<T: IntOrFloatTrait>(x: T) {
let v = x.to_int_or_float();
println!("{:?}", v);
}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_4(i);
attempt_4(f);
}
See it working here.
Here's another way that drops the enum. It's an iteration on Vladimir's answer.
trait Tr {
fn go(&self) -> ();
}
impl Tr for i32 {
fn go(&self) {
println!("i32")
}
}
impl Tr for f32 {
fn go(&self) {
println!("f32")
}
}
fn attempt_1<T: Tr>(t: T) {
t.go()
}
fn main() {
attempt_1(1 as i32);
attempt_1(1 as f32);
}
Function Overloading is Possible!!! (well, sorta...)
This Rust Playground example has more a more detailed example, and shows usage of a struct variant, which may be better for documentation on the parameters.
For more serious flexible overloading where you want to have sets of any number of parameters of any sort of type, you can take advantage of the From<T> trait for conversion of a tuple to enum variants, and have a generic function that converts tuples passed into it to the enum type.
So code like this is possible:
fn main() {
let f = Foo { };
f.do_something(3.14); // One f32.
f.do_something((1, 2)); // Two i32's...
f.do_something(("Yay!", 42, 3.14)); // A str, i32, and f64 !!
}
First, define the different sets of parameter combinations as an enum:
// The variants should consist of unambiguous sets of types.
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
Now, the conversion code; a macro can be written to do the tedious From<T> implementations, but here's what it could produce:
impl From<(i32, i32)> for FooParam {
fn from(p: (i32, i32)) -> Self {
FooParam::Bar(p.0, p.1)
}
}
impl From<f32> for FooParam {
fn from(p: f32) -> Self {
FooParam::Baz(p)
}
}
impl From<(&'static str, i32, f64)> for FooParam {
fn from(p: (&'static str, i32, f64)) -> Self {
FooParam::Qux(p.0, p.1, p.2)
}
}
And then finally, implement the struct with generic method:
struct Foo {}
impl Foo {
fn do_something<T: Into<FooParam>>(&self, t: T) {
use FooParam::*;
let fp = t.into();
match fp {
Bar(a, b) => print!("Bar: {:?}, {:?}\n", a, b),
Baz(a) => print!("Baz: {:?}\n", a),
Qux(a, b, c) => {
print!("Qux: {:?}, {:?}, {:?}\n", a, b, c)
}
}
}
}
Note: The trait bound on T needs to be specified.
Also, the variants need to be composed of combinations of types that the compiler wouldn't find ambiguous - which is an expectation for overloaded methods in other languages as well (Java/C++).
This approach has possibilities... it would be awesome if there's a decorator available - or one were written that did the From<T> implementations automatically when applied to an enum. Something like this:
// THIS DOESN'T EXIST - so don't expect the following to work.
// This is just an example of a macro that could be written to
// help in using the above approach to function overloading.
#[derive(ParameterOverloads)]
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
// If this were written, it could eliminate the tedious
// implementations of From<...>.
The Builder
Another approach that addresses the case where you have multiple optional parameters to an action or configuration is the builder pattern. The examples below deviate somewhat from the recommendations in the link. Typically, there's a separate builder class/struct which finalizes the configuration and returns the configured object when a final method is invoked.
One of the most relevant situations this can apply to is where you want a constructor that takes a variable number of optional arguments - since Rust doesn't have built-in overloading, we can't have multiple versions of ___::new(). But we can get a similar effect using a chain of methods that return self. Playground link.
fn main() {
// Create.
let mut bb = BattleBot::new("Berzerker".into());
// Configure.
bb.flame_thrower(true)
.locomotion(TractorTreads)
.power_source(Uranium);
println!("{:#?}", bb);
}
Each of the configuration methods has a signature similar to:
fn power_source(&mut self, ps: PowerSource) -> &mut Self {
self.power_source = ps;
self
}
These methods could also be written to consume self and return non-reference copies or clones of self.
This approach can also be applied to actions. For instance, we could have a Command object that can be tuned with chained methods, which then performs the command when .exec() is invoked.
Applying this same idea to an "overloaded" method that we want to take a variable number of parameters, we modify our expectations a bit and have the method take an object that can be configured with the builder pattern.
let mut params = DrawParams::new();
graphics.draw_obj(params.model_path("./planes/X15.m3d")
.skin("./skins/x15.sk")
.location(23.64, 77.43, 88.89)
.rotate_x(25.03)
.effect(MotionBlur));
Alternatively, we could decide on having a GraphicsObject struct that has several config tuning methods, then performs the drawing when .draw() is invoked.
Imagine I have functions like this:
fn min_max_difference(row: &Vec<u32>) -> u32 {
let mut min_elem: u32 = row[0];
let mut max_elem: u32 = min_elem;
for &element in row.iter().skip(1) {
if element < min_elem {
min_elem = element;
} else if element > max_elem {
max_elem = element;
}
}
result = max_elem - min_elem;
}
fn execute_row_operation(row: &Vec<u32>, operation: Fn(&Vec<u32>) -> u32) -> Option<(u32, u32)> {
let mut result = None;
if row.len() > 0 {
result = operation(row);
}
result
}
Note that the if block in execute_row_operation guarantees that the Vec<u32> I am passing to the operation function is non-empty. In general, I want "operations" to be functions which only accept non-empty rows. I would like it if I could do something like this:
fn min_max_difference<T: &Vec<u32> + NonEmpty>(row: T) -> u32 {
//snip
}
This would allow the compiler to disallow passing references to empty vectors to a function like min_max_difference which expects this.
But traits as I understand them specify what methods a type has, rather than what properties a type has. In my head, I am imagining a trait for a type T that is composed of boolean predicates with type: Fn<T> -> bool, and such a trait is "implemented" for a type if it all those predicates evaluate to true.
Can something like this be achieved?
Can a trait guarantee certain type properties
Yes, that is what they are for. In many cases, these properties are that a set of functions exist (e.g. PartialEq::eq) and that a set of behaviors are present (e.g. symmetric and transitive equality, required by PartialEq).
Traits can also have no methods, such as Eq. These only add a set of behaviors (e.g. reflexive equality). These types of traits are often referred to as marker traits.
such as a vector is non-empty?
However, you aren't asking for what you really want. You actually want a way to implement a trait for certain values of a type. This is not possible in Rust.
At best, you can introduce a newtype. This might be sufficient for your needs, but you could also implement your own marker traits for that newtype, if useful:
struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
fn new(v: Vec<T>) -> Result<Self, Vec<T>> {
if v.is_empty() {
Err(v)
} else {
Ok(NonEmptyVec(v))
}
}
}
fn do_a_thing<T>(items: NonEmptyVec<T>) {}
fn main() {
let mut a = Vec::new();
// do_a_thing(a); // expected struct `NonEmptyVec`, found struct `std::vec::Vec`
a.push(42);
let b = NonEmptyVec::new(a).expect("nope");
do_a_thing(b);
}
T: &Vec<u32> + NonEmpty
This isn't valid because Vec is a type and NonEmpty would presumably be a trait — you can't use types as trait bounds.
Historical note:
Way back in the long ago, as I understand it, Rust actually did support what you wanted under the name typestate. See What is typestate? and Typestate Is Dead, Long Live Typestate!.
An example of emulating it:
struct MyVec<T, S>
where
S: VecState,
{
vec: Vec<T>,
state: S,
}
trait VecState {}
struct Empty;
struct NonEmpty;
impl VecState for Empty {}
impl VecState for NonEmpty {}
impl<T> MyVec<T, Empty> {
fn new() -> Self {
MyVec {
vec: Vec::new(),
state: Empty,
}
}
fn push(mut self, value: T) -> MyVec<T, NonEmpty> {
self.vec.push(value);
MyVec {
vec: self.vec,
state: NonEmpty,
}
}
}
fn do_a_thing<T>(items: MyVec<T, NonEmpty>) {}
fn main() {
let a = MyVec::new();
// do_a_thing(a); // expected struct `NonEmpty`, found struct `Empty`
let b = a.push(42);
do_a_thing(b);
}
I have a function algo which works with a type S1, I also have
a type S2 which contains all of the fields of S1 plus some additional ones.
How should I modify algo to also accept S2 as input without
creating a temporary variable with type S1 and data from S2?
struct Moo1 {
f1: String,
f2: i32,
}
struct Moo2 {
f1: String,
f2: i32,
other_fields: f32,
}
struct S1 {
x: i32,
v: Vec<Moo1>,
}
struct S2 {
x: i32,
v: Vec<Moo2>,
}
//before fn algo(s: &S1)
fn algo<???>(???) {
//work with x and v (only with f1 and f2)
}
Where I'm stuck
Let's assume algo has this implementation (my real application has another implementation):
fn algo(s: &S1) {
println!("s.x: {}", s.x);
for y in &s.v {
println!("{} {}", y.f1, y.f2);
}
}
To access the field in Moo1 and Moo2 I introduce trait AsMoo, and to access x field and v I introduce trait AsS:
trait AsMoo {
fn f1(&self) -> &str;
fn f2(&self) -> i32;
}
trait AsS {
fn x(&self) -> i32;
// fn v(&self) -> ???;
}
fn algo<S: AsS>(s: &AsS) {
println!("s.x: {}", s.x());
}
I'm stuck at the implementation of the AsS::v method. I do not allocate memory to use my algo, but I need a Vec<&AsMoo> in some way.
Maybe I need to return some kind of Iterator<&AsMoo>, but have no idea how to do it and that looks complex for this problem.
Maybe I should use macros instead?
Any problem in computer science can be solved by adding another layer of indirection; at the exception of having too many such layers, of course.
Therefore, you are correct that you miss a S trait to generalize S1 and S2. In S, you can use a feature called associated type:
trait Moo {
fn f1(&self) -> &str;
fn f2(&self) -> i32;
}
trait S {
type Mooer: Moo;
fn x(&self) -> i32;
fn v(&self) -> &[Self::Mooer];
}
The bit type Mooer: Moo; says: I don't quite know what the exact type Mooer will end up being, but it'll implement the Moo trait.
This lets you write:
impl S for S1 {
type Mooer = Moo1;
fn x(&self) -> i32 { self.x }
fn v(&self) -> &[Self::Mooer] { &self.v }
}
impl S for S2 {
type Mooer = Moo2;
fn x(&self) -> i32 { self.x }
fn v(&self) -> &[Self::Mooer] { &self.v }
}
fn algo<T: S>(s: &T) {
println!("s.x: {}", s.x());
for y in s.v() {
println!("{} {}", y.f1(), y.f2());
}
}
And your generic algo knows that whatever type Mooer ends up being, it conforms to the Moo trait so the interface of Moo is available.