Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would I implement methods on a trait instead of as part of the trait?

Tags:

rust

traits

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.

like image 836
Shepmaster Avatar asked Dec 23 '15 15:12

Shepmaster


People also ask

How do you implement a trait?

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.

Can traits have properties Rust?

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.

How do you define a trait in Rust?

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.

What is implement in Rust?

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.


1 Answers

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).

like image 189
Francis Gagné Avatar answered Sep 25 '22 19:09

Francis Gagné