可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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`
回答1:
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();
}
}
回答2:
After reading https://stackoverflow.com/a/25819164/129805 it looks like Rust does automatic unboxing. Is this the case?
Not as automatic as you might think. In fact, you were looking for an unbox
method while Box<T>
implements Deref
for target T
. That means you should either call as_ref()
or rely on Deref
coercion. Note that a T
is not possible for unsized types, and since you are relying on polymorphic types, the consumer function will have to accept a reference.
I took the liberty of fixing main
and print_area
to make it work. The vector was also improperly declared as immutable.
fn print_area<T: HasArea + ?Sized>(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 mut vec: Vec<Box<HasArea>> = Vec::new();
vec.push(Box::new(c));
vec.push(Box::new(s));
for x in vec {
print_area(&*x)
}
}
回答3:
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.
回答4:
As an alternative to what E_net4 suggested, instead of boxing your traits you could use a Vec
with references to make it work:
fn print_area<T: HasArea+?Sized>(shape: &T) {
println!("This shape has an area of {}", shape.area());
}
let mut vec: Vec<&HasArea> = Vec::new();
vec.push(&c);
vec.push(&s);
for x in vec {
print_area(x)
}