Function pointers: expected type parameter found struct

Just out of curiosity, why does this code compile fine?

struct MyStruct {}
trait MyTrait {}

impl MyTrait for MyStruct {}

fn a_function<T: MyTrait>(arg: T) {}

fn foo<T: MyTrait>(fun: fn(T) -> (), arg: T) {
    fun(arg);
}

fn main() {
    let arg = MyStruct {};
    foo(a_function, arg);
}

And this one does not?

struct MyStruct {}
trait MyTrait {}

impl MyTrait for MyStruct {}

fn bar<T: MyTrait>(fun: fn(T) -> ()) {
    let arg = MyStruct {};
    fun(arg); //mismatched typesexpected type parameter `T`found struct `MyStruct`
}

fn main() {

}

What am I missing? Playground

T is decided by the caller, but you provide MyStruct from within your function. If MyTrait was implemented for u8 and the user decides to pass a function that takes fn(u8) as input, it would not get a u8 value as argument, but a value of MyStruct, breaking the contract of bar's signature. Generic type arguments are placeholders for concrete types (like u8 or MyStruct). If you want to pass something that implements MyTrait from inside your function, don't use a generic type argument, but either impl Trait[1] or a trait object. Unfortunately for you, you can't use impl Trait in function pointers, so you'd have to use a trait object instead:

fn bar(fun: fn(&dyn MyTrait) -> ()) {
    let arg = MyStruct {};
    fun(&arg);
}

Of course, in this toy example you provided you could just remove generics all together, but I assume this is not an option for your real problem:

fn bar(fun: fn(MyStruct) -> ()) {
    let arg = MyStruct {};
    fun(arg);
}

  1. See @quinedot's answer below ↩︎

4 Likes

This is exactly what I was missing! Thanks alot!

1 Like

impl Trait in argument position is a generic. In cases where it is allowed today, replacing a generic T with impl Trait in argument position means the same thing, only no one can name the latter. [1]

It's not inconceivable that fn(impl Trait) specifically will be special-cased to for<T: Trait> fn(T) some day, like how fn(&u8) means for<'x> fn(&'x u8). But that would be something new and not what you linked to, as for<T> types don't exist in Rust (so far).


  1. Switching between the two is often still a breaking change though, because it changes the turbofish signature of the function, if turbofishable. ↩︎

1 Like

Thanks for the clarification of my answer. Indeed did I confuse the behaviour of impl Trait in return vs. argument position: Impl trait type - The Rust Reference

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.