Problem working with closure parameters in traits

Hey there,

The code on the main function bellow works just fine, but when I try to do something similar on a trait implementation, it can't figure out the trait bounds of my closure, at least that's what I think is going on. What am I doing wrong here ?

#![allow(dead_code)]

struct Closures<F> {
    list: Vec<F>
}

impl<F> Closures<F>
where
    F: FnOnce() -> Result<(), &'static str>
{
    fn new() -> Self {
        Self { list: Vec::new() }
    }
    fn add(&mut self, f: F) -> &mut Self {
        self.list.push(f);
        self
    }
}

fn main(){
    let mut closures = Closures::new();
    
    closures.add(|| {
        if true {
            Ok(())
        } else {
            Err("An error")
        }
    });
    
    for closure in &closures.list {
        match closure() {
            Ok(_) => println!("OK!"),
            Err(err) => println!("{err}")
        }
    }
}

pub trait ClosureList {
    fn closures<F>(&self) -> Closures<F>
    where
        F: FnOnce() -> Result<(), &'static str>;
}

struct Test;

impl ClosureList for Test {
    fn closures<F>(&self) -> Closures<F> 
    where
        F: FnOnce() -> Result<(), &'static str>
    {
        let mut closures = Closures::new();
    
        closures.add(|| {
            if true {
                Ok(())
            } else {
                Err("An error")
            }
        });
        
        closures
    }
}

Error:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:62:9
   |
48 |     fn closures<F>(&self) -> Closures<F> 
   |                 -            ----------- expected `Closures<F>` because of return type
   |                 |
   |                 this type parameter
...
54 |         closures.add(|| {
   |                      -- the found closure
...
62 |         closures
   |         ^^^^^^^^ expected type parameter `F`, found closure
   |
   = note: expected struct `Closures<F>`
              found struct `Closures<[closure@src/main.rs:54:22: 54:24]>`
   = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `F`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

You have an incorrect mental model of what generics are.

A generic type parameter is always chosen by the caller. Remember, there's a turbofish syntax that can explicitly force a generic type parameter to be substituted with a specific type, but the same is true when type parameters are inferred – the compiler will infer the generic type parameters from the context of the call, not from the body of the function.

If you want the body of your function to determine the return type, then that can't possibly be a generic type parameter. For example, the following doesn't make any sense:

fn foo<T: Display>() -> T {
    "some string"
}

because even though &str: Display, what if the caller calls the function like foo::<u32>()? The type u32 is still Display, so the call should be valid, but the returned type is clearly not a u32, because it can only ever be a string, as the function returns a string literal.

To make the body determine the (opaque) return type, use impl Trait instead (Playground):

impl Test {
    fn closures(&self) -> Closures<impl FnOnce() -> Result<(), &'static str>> {
        ...
    }
}

Unfortunately, return-position impl Trait does not work in traits, yet. If you must have this function as a trait method (as opposed to an inherent one), then you'll have to return a Box<dyn Fn…>, like this:

impl ClosureList for Test {
    fn closures<F>(&self) -> Closures<Box<dyn FnOnce() -> Result<(), &'static str>>> {
        let mut closures: Closures<Box<dyn FnOnce() -> _>> = Closures::new();
    
        closures.add(Box::new(|| {
            if true {
                Ok(())
            } else {
                Err("An error")
            }
        }));
        
        closures
    }
}
6 Likes

Thank you, I think I understand now.

I believe I misunderstood because on the book it's stated that when passing traits as parameters the impl Trait syntax is sugar for the trait bound syntax:

The impl Trait syntax works for straightforward cases but is actually syntax sugar for a longer form known as a trait bound; it looks like this:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

I assumed it was the same when specifying a return value, Closures<F> would be the same as Closures<impl Whathever F is Bound To>.

No, impl Trait means these two completely different things in argument vs return position. This is pretty confusing, and argument-position impl Trait is useless/redundant because we already have generic type parameters. Therefore, I usually recommend that people not use argument-position impl Trait at all, confining its use only to return types.

1 Like