Difference between impl and where F: when returning closures

If one does this:

fn foo<F>() -> F
where
  F: FnMut() -> PathBuf
{
  || PathBuf::new()
}

The compiler says not to do this and use an impl instead:

fn foo() -> impl FnMut() -> PathBuf {
  || PathBuf::new()
}

And indeed this works.

The error in the first case is:

 1  error[E0308]: mismatched types
    --> src/main.rs:166:3
     |
 162 | fn foo<F>() -> F
     |        -       -
     |        |       |
     |        |       expected `F` because of return type
     |        |       help: consider using an impl return type: `impl FnMut() -> PathBuf`
     |        expected this type parameter
 ...
 166 |   || PathBuf::new()
     |   ^^^^^^^^^^^^^^^^^ expected type parameter `F`, found closure
     |
     = note: expected type parameter `F`
                       found closure `{closure@src/main.rs:166:3: 166:5}`
     = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `F`

I'm unable to wrap my brain around the final part: "every closure has a distinct type and so could not always match the caller-chosen type of parameter F".

Is it, in a roundabout way, hinting at "If you could use a generic, you could specify the type using foo::<SomeTime>(), but each closure is unique so we're not even going to give you an opportunity to try to do so"?

fn foo<F>() -> F is like str::parse -- it's saying that the caller gets to pick the type you'll return.

But if you're always returning the same closure, it can't possibly match every possible type that the caller could ask for.

2 Likes

Not really, it's not about denying the possibility. It's about who gets to choose the type:

  • Callers get to choose the type of generic parameters
  • Callee gets to choose the type of -> impl Trait

Forget closures for a second and consider this:

fn foo<S: std::fmt::Display>() -> S {
    "hi!"
}
error[E0308]: mismatched types
 --> src/lib.rs:3:5
  |
2 | fn foo<S: std::fmt::Display>() -> S {
  |        -                          -
  |        |                          |
  |        |                          expected `S` because of return type
  |        |                          help: consider using an impl return type: `impl std::fmt::Display`
  |        expected this type parameter
3 |     "hi!"
  |     ^^^^^ expected type parameter `S`, found `&str`
  |
  = note: expected type parameter `S`
                  found reference `&'static str`
  = note: the caller chooses a type for `S` which can be different from `&'static str`

The API says "for whatever S you, the caller, choose, I will return an S -- provided S implements Display". But then you went and tried to return a &str, instead of whatever the caller chose.

The solution is to put the choice in your (the callee's) hands.

fn foo() -> impl std::fmt::Display {
    "hi!"
}

And it's no different for closure types. If you (the callee) are returning one specific closure, a generic parameter is the wrong tool.

(Note also that the caller can "choose" not just by turbofish, but by context -- calling the function in some position where a specific output type is expected, say.)

4 Likes

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.