After reading this answer to "Vector of objects belonging to a trait", it looks like Rust does automatic unboxing. Is this the case?
My code doesn't compile and I don't understand how that answer's code could compile.
What is the correct way to unbox the elements of a polymorphic vector, one containing boxed traits?
I've read Rust by Example and the Box
documentation and I can't see any method that looks like unbox()
.
My code is:
trait HasArea {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 1.0f64,
};
print_area(c);
print_area(s);
let vec: Vec<Box<HasArea>> = Vec::new();
vec.push(Box::new(c));
vec.push(Box::new(s));
for x in vec {
print_area(x)
}
}
My error is:
Compiling rustgraph v0.1.0 (file:///home/chris/lunch/rustgraph)
error[E0277]: the trait bound `Box<HasArea>: HasArea` is not satisfied
--> src/main.rs:54:9
|
54 | print_area(x)
| ^^^^^^^^^^ the trait `HasArea` is not implemented for `Box<HasArea>`
|
= note: required by `print_area`
You can dereference it like print_area(*x)
, but it won't work for other reasons: the Sized
bound for the print_area
argument. Your function needs to know the size of its arguments.
You have other problems in your code: you are trying to push into an immutable vector and you are trying to box moved values. These were moved after you used it in print_area()
.
My opinion is that it would be easier to make print_area
a method which takes an immutable reference. This will work as you expected.
trait HasArea {
fn area(&self) -> f64;
fn print_area(&self) {
println!("This shape has area of {}", self.area());
}
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn print_area<T: HasArea>(shape: &T) {
println!("This shape has an area of {}", shape.area());
}
fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 1.0f64,
};
c.print_area();
s.print_area();
let mut vec: Vec<Box<HasArea>> = Vec::new();
vec.push(Box::new(c));
vec.push(Box::new(s));
for x in vec {
x.print_area();
}
}
To answer your direct question:
How to unbox elements contained in polymorphic vectors?
You cannot. Once something has been boxed and had the concrete type erased, that's it. A Box<SomeTrait>
cannot be made back into a SomeConcreteType
, because nothing knows what that concrete type is.
To solve the problem in the code... check the error message again:
the trait bound
Box<HasArea>: HasArea
is not satisfied
That's because a reference to a trait (or a box of a trait) does not implement that trait!
To allow your program to compile and run as you originally wrote it, you only need to implement the trait for boxes, and we might as well do references too:
impl<T: ?Sized> HasArea for Box<T>
where T: HasArea
{
fn area(&self) -> f64 { (**self).area() }
}
impl<'a, T: ?Sized> HasArea for &'a T
where T: HasArea
{
fn area(&self) -> f64 { (**self).area() }
}
This allows your fixed-up main to run:
fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 1.0f64,
};
print_area(&c);
print_area(&s);
let vec: Vec<Box<HasArea>> = vec![Box::new(c), Box::new(s)];
for x in vec {
print_area(x)
}
}
Here, we pass a reference of c
and s
to print_area
, to avoid transferring ownership. We also use the vec!
macro to construct the vector with much less ceremony.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With