Rust’s “impl dyn Trait” syntax

Today I saw for the first time the impl dyn Trait syntax in this PR. I had never seen this before and a quick search did not yield any results. After some experimentation I turned out that this syntax is quite useful.

The impl dyn Trait syntax allows you to implement methods that work for any object that implements a certain trait (including trait objects) without that method being part of the trait definition. In particular this means that implementer cannot override these methods and break contracts.

Let me use the following (very contrived) example to explain how this is useful. Assume you have a trait that allows you to convert a value into a bool. It would look like this.

trait AsBool {
  fn as_bool(&self) -> bool
}

Now it turns out that in your code that you often check if value that implements AsBool converts to false, for example

fn ban_user(am_i_admin: impl AsBool, other: User) {
  if (!am_i_admin.as_bool()) {
    panic!("Your not an admin!")
  }
  // ...
}

To make your code more dry and still have it work with any AsBool implementation you can add the following trait method with a default implementation

trait AsBool {
  fn as_bool(&self) -> bool;

  fn is_false(&self) -> bool {
    !self.as_bool()
  }
}

The problem with this approach is that implementors of AsBool might choose to override this implementation and violate the contract that x.as_bool() == !x.is_false().

A solution to this would be to write a static function

fn is_false(x: impl AsBool) -> bool {
  !x.as_bool()
}

However, using methods calls is often more convenient and idiomatic in Rust. This is where the impl dyn syntax comes into play. We can write the following:

impl dyn AsBool {
  fn is_false(&self) -> bool {
    !self.as_bool()
  }
}

This allows us to call x.is_false() whenever x implements AsBool or x is a reference or smart pointer to a value that implements AsBool.

3 Likes

That’s really cool! So basically it’s a method on any type that implements the trait, but not on the trait itself.

So, just to clarify that I’m getting this right, one can extend a trait with methods using the methods that are defined in that trait, without someone overriding the extended methods in the impl?

It’s pretty awesome, indeed. I found it while reading about trait objects in Rust. Some useful links I came across:

quick search did not yield any results

Yeah, it’s been a common experience of mine now getting into Rust.

It results in a virtual dispatch, though, so don’t use this in a tight loop! :point_up:

1 Like

Yes, indeed. That is correct

(TIL also that “Your posts must be at least 20 characters)

1 Like

Haha yes true, I guess everything marked dyn uses a runtime dispatch, kind of like the unsafe of performance.

lol, the longer explanation was useful :hugs:

Hi, I am a friend of Alex and while speaking Radicle Alex showed me a thread in here which eventually led me to this post.
just to try and give a little context, impl trait was introduced in Rust 1.26 to allow returning types identified by a Trait using static dispatch, previous to that the only way was using Trait objects (Box<dyn Trait>). In the argument position is the same as using fn foo<T: Trait>(arg: T) but with the disadvantage of not being able to use the turbo fish.
You can read more about it here.

1 Like

impl dyn implements a method on the Trait object so it won’t work with static dispatch: fn foo<T: Trait>(arg: T) or fn foo(arg: impl Trait).
It is used for example downcasting Trait objects into a specific type

I was just thinking about this again. Why would you do the above instead of just having a function:

fn is_false<B: AsBool>(x: B) -> bool 
{
  !x.as_bool() 
}

The only thing I can think of is that the method syntax might be more ergonomic to use. You wouldn’t need to import the function for example.

Right, that makes sense. It’s times like these where I miss a composition operator and not having this “method access” syntax :sweat_smile: