为什么有的 trait 能写成 dyn Trait,有的不行?这篇把「对象安全」(现在官方叫 dyn compatible)的几条规则逐条编译:每一条都有真实的 E0038 报错,再看 where Self: Sized 如何把不兼容的方法单独排除,以及克隆 trait 对象的惯用做法。
源码:
1 | trait Visitor { fn visit<T: std::fmt::Debug>(&self, t: T); }
2 | struct P; impl Visitor for P { fn visit<T: std::fmt::Debug>(&self, t: T) { println!("{:?}", t); } }
3 | fn main() { let v: Box<dyn Visitor> = Box::new(P); }
编译:失败
error[E0038]: the trait `Visitor` is not dyn compatible
--> <实验目录>/src/u42a.rs:3:28
|
3 | fn main() { let v: Box<dyn Visitor> = Box::new(P); }
| ^^^^^^^ `Visitor` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> <实验目录>/src/u42a.rs:1:20
|
1 | trait Visitor { fn visit<T: std::fmt::Debug>(&self, t: T); }
| ------- ^^^^^ ...because method `visit` has generic type parameters
| |
| this trait is not dyn compatible...
= help: consider moving `visit` to another trait
= help: only type `P` implements `Visitor`; consider using it directly instead.
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0038`. 源码:
1 | trait Dup { fn dup(&self) -> Self; }
2 | struct P; impl Dup for P { fn dup(&self) -> P { P } }
3 | fn main() { let v: Box<dyn Dup> = Box::new(P); }
编译:失败
error[E0038]: the trait `Dup` is not dyn compatible
--> <实验目录>/src/u42b.rs:3:28
|
3 | fn main() { let v: Box<dyn Dup> = Box::new(P); }
| ^^^ `Dup` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> <实验目录>/src/u42b.rs:1:30
|
1 | trait Dup { fn dup(&self) -> Self; }
| --- ^^^^ ...because method `dup` references the `Self` type in its return type
| |
| this trait is not dyn compatible...
= help: consider moving `dup` to another trait
= help: only type `P` implements `Dup`; consider using it directly instead.
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0038`.
编译:失败
error[E0038]: the trait `Make` is not dyn compatible
--> <实验目录>/src/u42c.rs:3:28
|
3 | fn main() { let v: Box<dyn Make> = Box::new(P); }
| ^^^^ `Make` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> <实验目录>/src/u42c.rs:1:17
|
1 | trait Make { fn make() -> Self; fn name(&self) -> String; }
| ---- ^^^^ ...because associated function `make` has no `self` parameter
| |
| this trait is not dyn compatible...
= help: only type `P` implements `Make`; consider using it directly instead.
help: consider turning `make` into a method by giving it a `&self` argument
|
1 | trait Make { fn make(&self) -> Self; fn name(&self) -> String; }
| +++++
help: alternatively, consider constraining `make` so it does not apply to trait objects
|
1 | trait Make { fn make() -> Self where Self: Sized; fn name(&self) -> String; }
| +++++++++++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0038`. 源码:
1 | trait Shape: Clone { fn area(&self) -> f64; }
2 | #[derive(Clone)] struct Sq(f64); impl Shape for Sq { fn area(&self) -> f64 { self.0 } }
3 | fn main() { let v: Box<dyn Shape> = Box::new(Sq(1.0)); }
编译:失败
error[E0038]: the trait `Shape` is not dyn compatible
--> <实验目录>/src/u42d.rs:3:28
|
3 | fn main() { let v: Box<dyn Shape> = Box::new(Sq(1.0)); }
| ^^^^^ `Shape` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> <实验目录>/src/u42d.rs:1:14
|
1 | trait Shape: Clone { fn area(&self) -> f64; }
| ----- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait is not dyn compatible...
= help: only type `Sq` implements `Shape`; consider using it directly instead.
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0038`. 源码:
1 | trait Shape {
2 | fn area(&self) -> f64;
3 | fn scaled(&self, k: f64) -> Self where Self: Sized; // 只有具体类型才有,dyn 上没有
4 | fn describe<T: std::fmt::Display>(&self, tag: T) -> String where Self: Sized { format!("{tag}: {}", self.area()) }
5 | }
6 | struct Sq(f64);
7 | impl Shape for Sq { fn area(&self) -> f64 { self.0 * self.0 } fn scaled(&self, k: f64) -> Sq { Sq(self.0 * k) } }
8 | fn main() {
9 | let s = Sq(2.0);
10 | println!("具体类型可以用全部方法:{} {}", s.scaled(2.0).area(), s.describe("sq"));
11 | let d: Box<dyn Shape> = Box::new(Sq(3.0));
12 | println!("dyn 上只有对象安全的方法:{}", d.area());
13 | }
编译:成功
运行(退出码 0):
具体类型可以用全部方法:16 sq: 4
dyn 上只有对象安全的方法:9
编译:失败
error: the `scaled` method cannot be invoked on a trait object
--> <实验目录>/src/u42f.rs:4:58
|
1 | trait Shape { fn area(&self) -> f64; fn scaled(&self, k: f64) -> Self where Self: Sized; }
| ----- this has a `Sized` requirement
...
4 | fn main() { let d: Box<dyn Shape> = Box::new(Sq(1.0)); d.scaled(2.0); }
| ^^^^^^
error: aborting due to 1 previous errorvtable 的结构见Trait Object 与动态分发。
源码:
1 | trait Shape { fn area(&self) -> f64; fn box_clone(&self) -> Box<dyn Shape>; }
2 | #[derive(Clone)] struct Sq(f64);
3 | impl Shape for Sq { fn area(&self) -> f64 { self.0 * self.0 } fn box_clone(&self) -> Box<dyn Shape> { Box::new(self.clone()) } }
4 | impl Clone for Box<dyn Shape> { fn clone(&self) -> Self { self.box_clone() } }
5 | fn main() {
6 | let a: Box<dyn Shape> = Box::new(Sq(3.0));
7 | let b = a.clone();
8 | println!("克隆出的 trait 对象:{} {}", a.area(), b.area());
9 | }
编译:成功
运行(退出码 0):
克隆出的 trait 对象:9 9