Help in understanding impl trait for closure

So, I was implementing the multithreaded server as in the rust introductory book. But, I got this compiler error

 type Job = impl FnOnce();
   |              ----------------- the expected opaque type
...
82 |           pool.execute(|| {
   |  ______________________^
83 | |             handle_connection(stream);
84 | |         });
   | |_________^ expected opaque type, found closure
   |
   = note: expected opaque type `impl std::ops::FnOnce<()>`
                  found closure `[closure@src/main.rs:82:16: 84:4 stream:_]`

But the closure exactly implements the FnOnce. But here, in another example:

fn multiplier(num: i32) -> impl Fn(i32) -> i32 {
	let mul =move |x:i32| x * num;
	println!("{}", num);
	mul
}

This fuction compiles successfully. But, I don't understand why is it giving the error for that closure.

You can't use impl Trait in type aliases.

error[E0658]: `impl Trait` in type aliases is unstable
 --> src/lib.rs:2:12
  |
2 | type Job = impl FnOnce();
  |            ^^^^^^^^^^^^^
  |
  = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information

Even if you enable the unstable feature, it doesn't mean what you think it does. The impl Trait syntax is not a single type, rather it is syntax sugar for defining a generic function when used as an argument, but when used in a type alias it means something else.

Ok, I'm actually confused rn.
So, if try to use impl FnOnce() in struct or type. It will give error like error[E0562]: ``impl Trait`` not allowed outside of function and inherent method return types. But, it is perfectly valid to write like this

struct<T> Ex(T);
impl<T: FnOnce()> Ex<T> { ..something.. }

But, it wrong to write like this functions. For example

fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
    || {
        println!("this function was called");
        func();
    }
}

It will give with give with the error

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
9  |   fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
   |                                  - this type parameter    - expected `T` because of return type
10 | /     || {
11 | |         println!("this function was called");
12 | |         func();
13 | |     }
   | |_____^ expected type parameter `T`, found closure
   |
   = note: expected type parameter `T`
                     found closure `[closure@src/main.rs:10:5: 13:6 func:_]`

but, if I change the generic T to impl FnOnce() like below. It correctly compiles.

fn decorator_with_impl(func: impl FnOnce()) -> impl FnOnce() {
    || {
        println!("this function was called");
        func();
    }
}

As far I understand both are essentially doing the same thing as impl is a syntactic sugar over generics. So, why is it that I can't use impl Trait in structs but it's fine in functions. And also why using generics on the trait works on Sturcts but not in functions?

Generally the main thing to understand is that there is not one single FnOnce() type, rather every single closure in the program has its own unique type, and each such type implements the FnOnce() trait. The thing is that when dealing with code that uses closures, you need to deal with two situations:

  1. You want to talk about all closures. This is the job of generics.
  2. You want to talk about a specific closure. This is the job of existential types.

Generally to make matters more confusing, the impl Trait syntax is sometimes syntax sugar for generics, and sometimes the syntax used for existential types. Let's consider some examples.

fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
    || {
        println!("this function was called");
        func();
    }
}

In this particular case, you take one closure as an argument, and return some other closure as the return value. In this case you want the argument to be able to take any closure as argument, but the return type should always be the specific closure you specified in the function body, not some other closure from somewhere else. So you want generics for the argument and existentials for the return type. The example doesn't compile because you claim to return the same closure type as you were given as argument, but you actually return some other closure.

The way you use existential types is the impl Trait syntax, like this:

fn decorator_with_generic_type<T: FnOnce()>(func: T) -> impl FnOnce() {
    || {
        println!("this function was called");
        func();
    }
}

This signature says "the function can take any closure type, let's call it T, and it will return some specific closure type".

Now what about your example with impl Trait in the argument position? Well that's where the confusing part comes in. When impl Trait is used as an argument instead of a return type, it instead becomes syntax sugar for generics. Thus your example is syntax sugar for my example above.

Generics are always generics and never an existential type. Your original function would compile if you had returned the same closure, because then the closure types really do match.

fn decorator_with_generic_type<T: FnOnce()>(func: T) -> T {
    func
}

In structs you typically always want to use generics. It's not possible to use the impl Trait syntax in a struct directly. As for what type Foo = impl Trait means, well that's an existential type. This means that you can only ever have such a type alias refer to a single specific closure, for example trying to claim that two closures have the same existential type will fail to compile:

type MyFunc = impl FnOnce();

fn foo1() -> MyFunc {
    || {
        println!("Hi!");
    }
}

fn foo2() -> MyFunc {
    || {
        println!("Hello :)");
    }
}
error: concrete type differs from previous defining opaque type use
  --> src/lib.rs:10:1
   |
10 | fn foo2() -> MyFunc {
   | ^^^^^^^^^^^^^^^^^^^ expected `[closure@src/lib.rs:5:5: 7:6]`, got `[closure@src/lib.rs:11:5: 13:6]`
   |
note: previous use here
  --> src/lib.rs:4:1
   |
4  | fn foo1() -> MyFunc {
   | ^^^^^^^^^^^^^^^^^^^

However if you make sure that its the same closure in both cases, it works:

type MyFunc = impl FnOnce();

fn foo1() -> MyFunc {
    make_foo("Hi!")
}

fn foo2() -> MyFunc {
    make_foo("Hello :)")
}

fn make_foo(s: &'static str) -> MyFunc {
    move || {
        println!("{}", s);
    }
}
3 Likes

Thanks a lot. It is really good explanation. I finally got that mental model for where to use generics and impl

1 Like