Reputation: 59368
There are situations where you want a factory function to create some data, but for whatever reasons the data you return must contain references. This does not seem possible because you cannot return references from functions.
Let's look at the following example code:
// Important: traits are of unknown size, and so must be passed around
// as a reference
trait Shape {
fn area(&self) -> f32;
}
// As seen here.
struct ShapeCollection<'a> {
shapes: Vec<&'a Shape>
}
// This function needs to return a ShapeCollection, but since the
// data structure requires references to work, it seems impossible!
fn make_shapes<'a>(needRectangle: bool, multiplier: f32) -> ShapeCollection<'a> {
let rect = Rectangle {
width: 42.0 * multiplier,
height: 24.0 * multiplier
};
let circle = Circle {
radius: 42.0 * multiplier
};
match needRectangle {
true => ShapeCollection {
shapes: vec![&rect, &circle]
},
false => ShapeCollection {
shapes: vec![&circle]
},
}
}
// ^ This function won't compile because rect and circle do not
// life long enough, and the compiler dies at this point
// Impls if you're interested / want to compile, but not that important
struct Rectangle {
width: f32,
height: f32
}
impl Shape for Rectangle {
fn area(&self) -> f32 {
self.width * self.height
}
}
struct Circle {
radius: f32
}
impl Shape for Circle {
fn area(&self) -> f32 {
(std::f32::consts::PI * self.radius).powf(2f32)
}
}
This is a simplified example built out of a more complex code I am writing. The gist of the issue is two conflating requirements:
Vec
must contain references not valuesHow can I solve both of these requirements in Rust?
The two choices seems to be:
Upvotes: 2
Views: 1070
Reputation: 430368
You simply cannot return a reference to a variable created in a function.
Important: traits are of unknown size, and so must be passed around as a reference
This isn't strictly true. They must be passed around via some kind of indirection, but a reference (&
) isn't the only kind of indirection that a trait object can use. There's also boxed trait objects:
struct ShapeCollection {
shapes: Vec<Box<Shape>>,
}
fn make_shapes(need_rectangle: bool, multiplier: f32) -> ShapeCollection {
let rect = Rectangle {
width: 42.0 * multiplier,
height: 24.0 * multiplier,
};
let circle = Circle {
radius: 42.0 * multiplier,
};
match need_rectangle {
true => ShapeCollection {
shapes: vec![Box::new(rect), Box::new(circle)],
},
false => ShapeCollection {
shapes: vec![Box::new(circle)],
},
}
}
In 99.9% of the cases, a function signature with a lifetime that's only in the return position can never work (or doesn't do what you want) and should always give you pause:
fn make_shapes<'a>(need_rectangle: bool, multiplier: f32) -> ShapeCollection<'a>
Note that the Rust style is snake_case
, so I've renamed need_rectangle
.
See also:
Upvotes: 2
Reputation: 59368
One possibility seems to be to use enums instead of traits for polymorphism.
For this example that would be:
enum Shape2 {
Rectangle { width: f32, height: f32 },
Circle { radius: f32 }
}
fn area(shape: Shape2) -> f32 {
match shape {
Shape2::Rectangle {width, height} => width * height,
Shape2::Circle {radius} => (std::f32::consts::PI * radius).powf(2f32)
}
}
struct Shape2Collection {
shapes: Vec<Shape2>
}
fn make_shapes2(needRectangle: bool, multiplier: f32) -> Shape2Collection {
let rect = Shape2::Rectangle {
width: 42.0 * multiplier,
height: 24.0 * multiplier
};
let circle = Shape2::Circle {
radius: 42.0 * multiplier
};
match needRectangle {
true => Shape2Collection {
shapes: vec![rect, circle]
},
false => Shape2Collection {
shapes: vec![circle]
},
}
}
The downside of this is that your implementations aren't methods anymore, but are functions, and that your implementations are smushed together in the same function.
Upvotes: 0