Impl Trait as type parameter and as return type - monomorphization or dynamic dispatch?

Consider this:

fn hello(t: impl AsRef<str>) -> impl Iterator<Item=u8> {
    // inconsequential impl
}

Suppose at call site, caller does like so:

let s = "hey";
let iterator = hello(s);

Does Rust monomorphize hello?

Suppose alternatively:

let o: Box<dyn AsRef<str>> = Box::new("hey".to_string());
let iterator = hello(o);

Does Rust then uses v-table look up to find the impl of AsRef<str> for o?

yes

In this case, the AsRef implementation of Box would call... wait, nope, the code doesn't compile.

error[E0277]: the trait bound `Box<dyn AsRef<str>>: AsRef<str>` is not satisfied
 --> src/main.rs:6:22
  |
6 | let iterator = hello(o); 
  |                ----- ^ the trait `AsRef<str>` is not implemented for `Box<dyn AsRef<str>>`
  |                |
  |                required by a bound introduced by this call
  |
  = help: the trait `AsRef<T>` is implemented for `Box<T, A>`
note: required by a bound in `hello`
 --> src/main.rs:1:18
  |
1 | fn hello(t: impl AsRef<str>) -> impl Iterator<Item=u8> {
  |                  ^^^^^^^^^^ required by this bound in `hello`

Unless you meant to change the signature of hello?

  • impl Trait in argument position is equivalent to a generic type parameter.
  • impl Trait in return position is nothing but a concrete type hidden behind a black box that only lets you use methods from the specified trait (a so-called "existential type").

Neither of these involves dynamic dispatch in itself. dyn Trait is another implementation of the idea of an existential type, but this doesn't mean that it would be the same as return-position impl Trait.

2 Likes

Sorry about it, been busy, I will churn out a compilable snippet asap. Thank you.

Does it mean that at the callsite, if the caller passes values of concrete types as arguments to the hello function, Rust will do static dispatch, i.e. monomorphization, over all the concrete types passed into hello?

And a related question is, if a trait object is passed as an argument to hello, will Rust do dispatch dispatch over it at runtime?

Thank you for your response.

Yes, fn hello(t: impl AsRef<str>) is the same as fn hello<T: AsRef<str>>(t: T) except that you aren't allowed to turbofish the type (but that's merely a syntactic difference).

I can't really interpret that question. A trait object does always involve dynamic dispatch. (At least conceptually, ignoring optimizations.)

If you pass a trait object to hello, then hello will be monomorphized with the (hidden) type parameter equal to the type of the trait object, e.g. &dyn AsRef<str>, which is itself a concrete type. The trait object will then forward to the underlying concrete type using dynamic dispatch, as always.

There's nothing special happening here. There is no distinguished interaction between generics and trait objects. It's just a composition of how the individual features work separately.

1 Like

I get it now @H2CO3, you're saying function dispatching over generic type parameters has nothing (at least conceptually) to do with using impl Trait in both the type specifications of function parameter and return type.

No, that's not what I'm saying at all.

On the contrary. impl Trait in argument position is almost exactly the same as a generic type parameter except for the the aforementioned minor syntactic caveat.

What I was saying is that trait objects have nothing to do with that.

Trait objects are dyn Trait types. impl Trait is not a trait object.

I've written up some comparisons between dyn Trait, impl Trait, and generics here.

Depending on how familiar with dyn Trait you are, it may help to read out what dyn Trait is first.

2 Likes