In Rust, why are trait objects usually used via references (&dyn Trait
) or smart Pointers (like Box<dyn Trait>
)? Does it have to be? Or is it better to use it this way?
They are handled via references and smart pointers because they won't compile otherwise:
trait Foo {}
struct Bar;
impl Foo for Bar {}
fn main() {
let f1: dyn Foo = Bar;
let f2: dyn Foo = Bar as dyn Foo;
}
error[E0308]: mismatched types
--> src/main.rs:6:23
|
6 | let f1: dyn Foo = Bar;
| ------- ^^^ expected `dyn Foo`, found `Bar`
| |
| expected due to this
|
= note: expected trait object `dyn Foo`
found struct `Bar`
= help: `Bar` implements `Foo` so you could box the found value and coerce it to the trait object `Box<dyn Foo>`, you will have to change the expected type as well
error[E0620]: cast to unsized type: `Bar` as `dyn Foo`
--> src/main.rs:7:23
|
7 | let f2: dyn Foo = Bar as dyn Foo;
| ^^^^^^^^^^^^^^
|
help: consider using a box or reference as appropriate
--> src/main.rs:7:23
|
7 | let f2: dyn Foo = Bar as dyn Foo;
| ^^^
error[E0277]: the size for values of type `dyn Foo` cannot be known at compilation time
--> src/main.rs:6:9
|
6 | let f1: dyn Foo = Bar;
| ^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `dyn Foo`
= note: all local variables must have a statically known size
= help: unsized locals are gated as an unstable feature
help: consider borrowing here
|
6 | let f1: &dyn Foo = Bar;
| +
A dyn Trait
is a dynamic type that represents any potential concrete type that implements that Trait
. Since it is representing a variety of types of potentially different sizes, it itself does not have a fixed size. This is called a dynamically sized type (DST).
Because they don't have a fixed size, there are limits to where we can put them. In particular a DST cannot be stored in a variable; and if stored in a struct, it makes that struct a DST which therefore also cannot be stored in a variable. There is a unsized locals RFC that may make it possible in the future, but right now its fate is unclear.
Side note: you can still reference a variable as a dynamic trait but that is because the variable is still a known concrete type even though the trait object accesses it dynamically:
fn main() {
let b = Bar;
let f: &dyn Foo = &b;
}
So in order to work with a DST, it requires some indirection; the value must be stored elsewhere and only referred to through a reference or smart pointer like Box
(Rc
, Arc
, also work).
Technically tangential but worth mentioning anyway: references and smart pointers to trait objects are "fat". This means they actually store two pointers: one to the data and another to a v-table that maps to the trait functions associated with that trait implementation. See What is a "fat pointer"? for more info.