While trying to understand the Any
trait better, I saw that it has an impl
block for the trait itself. I don't understand the purpose of this construct, or even if it has a specific name.
I made a little experiment with both a "normal" trait method and a method defined in the impl
block:
trait Foo { fn foo_in_trait(&self) { println!("in foo") } } impl dyn Foo { fn foo_in_impl(&self) { println!("in impl") } } impl Foo for u8 {} fn main() { let x = Box::new(42u8) as Box<dyn Foo>; x.foo_in_trait(); x.foo_in_impl(); let y = &42u8 as &dyn Foo; y.foo_in_trait(); y.foo_in_impl(); // May cause an error, see below }
Editor's note
In versions of Rust up to and including Rust 1.15.0, the line
y.foo_in_impl()
causes the error:error: borrowed value does not live long enough --> src/main.rs:20:14 | 20 | let y = &42u8 as &Foo; | ^^^^ does not live long enough ... 23 | } | - temporary value only lives until here | = note: borrowed value must be valid for the static lifetime...
This error is no longer present in subsequent versions, but the concepts explained in the answers are still valid.
From this limited experiment, it seems like methods defined in the impl
block are more restrictive than methods defined in the trait
block. It's likely that there's something extra that doing it this way unlocks, but I just don't know what it is yet! ^_^
The sections from The Rust Programming Language on traits and trait objects don't make any mention of this. Searching the Rust source itself, it seems like only Any
and Error
use this particular feature. I've not seen this used in the handful of crates where I have looked at the source code.
To implement a trait, declare an impl block for the type you want to implement the trait for. The syntax is impl <trait> for <type> . You'll need to implement all the methods that don't have default implementations.
Nothing in Rust prevents a trait from having a method with the same name as another trait's method, nor does Rust prevent you from implementing both traits on one type. It's also possible to implement a method directly on the type with the same name as methods from traits.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
An implementation is an item that associates items with an implementing type. Implementations are defined with the keyword impl and contain functions that belong to an instance of the type that is being implemented or to the type statically. There are two types of implementations: inherent implementations.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With