Why would I implement methods on a trait instead of as part of the trait?
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).
I suspect that the reason is very simple: may be overridden or not?
A method implemented in a trait
block can be overridden by implementors of the trait
, it just provides a default.
On the other hand, a method implemented in an impl
block cannot be overridden.
If this reasoning is right, then the error you get for See Francis Gagné's more complete answer on the interaction with lifetimes.y.foo_in_impl()
is just a lack of polish: it should have worked.