When you define a trait named Foo
that can be made into an object, Rust also defines a trait object type named dyn Foo
. In older versions of Rust, this type was only called Foo
(see What does "dyn" mean in a type?). For backwards compatibility with these older versions, Foo
still works to name the trait object type, although dyn
syntax should be used for new code.
Trait objects have a lifetime parameter that designates the shortest of the implementor's lifetime parameters. To specify that lifetime, you write the type as dyn Foo + 'a
.
When you write impl dyn Foo {
(or just impl Foo {
using the old syntax), you are not specifying that lifetime parameter, and it defaults to 'static
. This note from the compiler on the y.foo_in_impl();
statement hints at that:
note: borrowed value must be valid for the static lifetime...
All we have to do to make this more permissive is to write a generic impl
over any lifetime:
impl<'a> dyn Foo + 'a {
fn foo_in_impl(&self) { println!("in impl") }
}
Now, notice that the self
argument on foo_in_impl
is a borrowed pointer, which has a lifetime parameter of its own. The type of self
, in its full form, looks like &'b (dyn Foo + 'a)
(the parentheses are required due to operator precedence). A Box<u8>
owns its u8
– it doesn't borrow anything –, so you can create a &(dyn Foo + 'static)
out of it. On the other hand, &42u8
creates a &'b (dyn Foo + 'a)
where 'a
is not 'static
, because 42u8
is put in a hidden variable on the stack, and the trait object borrows this variable. (That doesn't really make sense, though; u8
doesn't borrow anything, so its Foo
implementation should always be compatible with dyn Foo + 'static
... the fact that 42u8
is borrowed from the stack should affect 'b
, not 'a
.)
Another thing to note is that trait methods are polymorphic, even when they have a default implementation and they're not overridden, while inherent methods on a trait objects are monomorphic (there's only one function, no matter what's behind the trait). For example:
use std::any::type_name;
trait Foo {
fn foo_in_trait(&self)
where
Self: 'static,
{
println!("{}", type_name::<Self>());
}
}
impl dyn Foo {
fn foo_in_impl(&self) {
println!("{}", type_name::<Self>());
}
}
impl Foo for u8 {}
impl Foo for u16 {}
fn main() {
let x = Box::new(42u8) as Box<dyn Foo>;
x.foo_in_trait();
x.foo_in_impl();
let x = Box::new(42u16) as Box<Foo>;
x.foo_in_trait();
x.foo_in_impl();
}
Sample output:
u8
dyn playground::Foo
u16
dyn playground::Foo
In the trait method, we get the type name of the underlying type (here, u8
or u16
), so we can conclude that the type of &self
will vary from one implementer to the other (it'll be &u8
for the u8
implementer and &u16
for the u16
implementer – not a trait object). However, in the inherent method, we get the type name of dyn Foo
(+ 'static
), so we can conclude that the type of &self
is always &dyn Foo
(a trait object).