FnOnce: Code doesn't compile and I don't see the issue

I am stuck with some snippet of code which can be simplified to the following:

struct A {
    x: u16,
}

trait B {
    fn print_self(&self);
}


impl B for A {
    fn print_self(&self) {
        println!("X = {}", self.x);
    }
}

fn invoke<F, T>(f: F)
where
    T: B,
    F: FnOnce(&mut T),
{
    let mut a = A{x: 42};
    f(&mut a);
}

fn main() {
    invoke(|b| {
        b.print_self();
    })
}

Basically, the A struct implements the B trait, and I am trying to pass the generic B parameter to the function in lambda. The compilation error is not helping at all:

error[E0308]: mismatched types
  --> src\main.rs:22:7
   |
22 |     f(&mut a);
   |       ^^^^^^ expected type parameter, found struct `A`
   |
   = note: expected type `&mut T`
              found type `&mut A`

error[E0282]: type annotations needed
  --> src\main.rs:26:13
   |
26 |     invoke(|b| {
   |             ^ consider giving this closure parameter a type
   |
   = note: type must be known at this point

error: aborting due to 2 previous errors

Looks like I messed the syntax up, but I don’t see where. Please help.

Since the type parameter T of invoke must always be A to pass typecheck try:

fn invoke<F>(f: F)
where
    F: FnOnce(&mut A),
{
    let mut a = A{x: 42};
    f(&mut a);
}

alternatively and i suspect this is what you really want, pass in t as a parameter:

fn invoke<F,T>(f: F, t: &mut T)
where
    T: B,
    F: FnOnce(&mut T),
{
    
    f(t);
}

First error message is the key point to understand this error. In the signature of fn invoke, a f is a function that can be called once, with single parameter which has type T. Problem is the T doesn’t need to same as A. For example, user may define struct C and impl B for C on it, and call invoke with closure that expect to receive a parameter with type C.

But how about the second error? Your invoke is a generic function with two type parameters, T and F. Type parameters of generic functions must be resolved by caller. In normal case type parameters are resolved by type inference, but in your code it’s not obvious at the caller side what exactly is the type T. So you need to annotate its type by hand.

1 Like

Type parameters in a generic function are chosen by the caller of the function, so

fn invoke<F, T>(f: F)
where
    T: B,
    F: FnOnce(&mut T),
{
    let mut a = A{x: 42};
    f(&mut a);
}

is saying that whatever type T of argument of the FnOnce closure the function may be given, you will feed an element of type A instead. Since there exist types T that are not A (all of them but A), then this is an error.

For a more concrete example, nothing prevents someone from calling your function with:

invoke
    ::<fn(&mut i32), i32> // usually inferred; here T = i32 != A
    (|x: &mut i32| *x += 1);

In which case you would be giving a &mut A { ... } instead of a &mut i32

I found that there already are questions like mine dated 4 years ago (this one, for example), yet I haven’t managed to formulate mine correctly the first time.

I successfully implemented what I intended like follows:

struct A {
    x: u16,
}

struct B {
    y: bool,
}

trait Print {
    fn print_self(&self);
}


impl Print for A {
    fn print_self(&self) {
        println!("Number = {}", self.x);
    }
}

impl Print for B {
    fn print_self(&self) {
        println!("Boolean = {}", self.y);
    }
}

fn invoke<F>(is_num: bool, f: F)
where
    F: FnOnce(&mut Print),
{
    match is_num {
        true => f(&mut A { x: 42 }),
        false => f(&mut B { y: true })
    }
}

fn main() {
    invoke(false, |b| {
        b.print_self();
    })
}

The trick is that I completely removed the T parameter from the generic function definition, and it worked and ran successfully as intended. I guess merely having this parameter in function definition made it wrong. Thanks everyone for answers, it helped.