Why does adding a bound (that is fulfilled) make this code fail to compile?

Why does adding a bound (that is fulfilled) make this code fail to compile?

 struct Foo<F> {
     func: F,
 }
 
 impl<F> Foo<F> {
-    fn new(func: F) -> Self
+    fn new<R>(func: F) -> Self
+    where
+        F: FnMut() -> R,
     {
         Foo { func }
     }
 }
 
 impl<R> Foo<R> {
     fn call(&self)
     where
         R: Fn() -> (),
     {
         (self.func)()
     }
 }
 
 fn main() {
     let foo = Foo::new(|| println!("called"));
     foo.call();
 }

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: expected a `Fn<()>` closure, found `[closure@src/main.rs:28:24: 28:26]`
  --> src/main.rs:29:9
   |
29 |     foo.call();
   |         ^^^^ expected an `Fn<()>` closure, found `[closure@src/main.rs:28:24: 28:26]`
   |
   = help: the trait `Fn<()>` is not implemented for closure `[closure@src/main.rs:28:24: 28:26]`
   = note: wrap the `[closure@src/main.rs:28:24: 28:26]` in a closure with no arguments: `|| { /* code */ }`
   = note: `[closure@src/main.rs:28:24: 28:26]` implements `FnMut`, but it must implement `Fn`, which is more general
note: required by a bound in `Foo::<R>::call`
  --> src/main.rs:21:12
   |
19 |     fn call(&self)
   |        ---- required by a bound in this associated function
20 |     where
21 |         R: Fn() -> (),
   |            ^^^^^^^^^^ required by this bound in `Foo::<R>::call`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (bin "playground") due to previous error

I want to add that bound to avoid some type inference issues with arguments, but I don't see a reason to make new demand an Fn (even FnOnce would be sufficient there).

If I replace FnMut in the bound of the new method with Fn, then it does compile (Playground). It will also compile if I uncomment the foo.call() (Playground).

1 Like

Your code works with the following change in main:

fn main() {
-    let foo = Foo::new(|| println!("called"));
+    let closure = || println!("called");
+    let foo = Foo::new(closure);
    foo.call();
}

The bound on Foo::new is FnMut so the closure is coerced to and treated as an FnMut. Once it's "stored" as an FnMut, all other type information about the closure is lost (I am speaking imprecisely because this is just how I understand it, not a precise technical explanation).
So when you try to call Foo::call, rust sees that the stored closure implements FnMut, but it doesn't implement Fn, which is the stricter bound. Even though the closure technically does implement Fn, rust doesn't know that because all it knows is that the closure is a FnMut, nothing more.

When you separate out the closure creation from the call, you create the closure in a place where it isn't automatically coerced into an FnMut.

1 Like

Interesting! I created a Playground to demonstrate.

So I guess it could be considered a compiler bug? Or is there a (good) reason why inference fails?

1 Like

I guess this is another of those "implied bounds" issues.

Can you elaborate? (Or do you have a link to a corresponding issue?)

Should I report this as an issue?

I believe this behavior is intended. You don't need to report an issue.
I'll admit it is a sharp corner of rust though.

Note that FnMut is a trait, and that F: FnMut() -> R is a bound, not a type! I don't see a reason why that bound should infer a type that fulfills less traits.

1 Like

As far as I'm aware, Fn*-trait bounds from type inference influence closure type a lot. The presence of a, FnMut killing the Fn implementation is just one example.

Another example is how it's entirely impossible to get proper dependencies between lifetimes in arguments and return types, without the presence of such a trait bound. E. g. try to write a for<'a> Fn(&'a u8) -> &'a u8 without that trait bound being visible for type inference at the place the closure is defined; IIRC that won't work.

E. g. Rust Playground

Maybe it's similar to #26085?

2 Likes

That issue describes exactly the behavior you describe here, AFAICT.

2 Likes

I think it's possible in nightly Rust?

#![feature(closure_lifetime_binder)]

fn f(_: impl for<'a> Fn(&'a u8) -> &'a u8) {} 

fn test() {
    // works
    f(|x: &u8| x);
    
    // works now too
    let c = for<'a> |x: &'a u8| -> &'a u8 { x };
    f(c);
}

(Playground)

Looks like the issue you linked lists a reason in the discussion here: the standard inference of whether a closure is also FnMut or only FnOnce will infer "also FnMut" for certain closures where an FnMut impl will subsequently run into borrow check errors, whilst an FnOnce-only implementation wouldn't.

I wonder whether similar examples exist for Fn vs only-FnMut.

I suppose this is not a water tight argument against changing the status quo, but it makes the situation harder to improve, especially if the goal is (as it always should be) that borrow checking should not influence program behavior (so deciding whether a trait is implemented based on whether borrow checking some code succeeds is probably a bad idea). In principle, I can imaging the compiler being/becoming smarter at discovering (and thus considering) this indirect Fn requirement in your code (within the main function body) though.

2 Likes

Indeed, it is ::tada:. My statement was meant to only cover what's possible on stable :wink:

In case of the FnOnce problem, why can't this solved by the compiler?

fn fn_once<'a, T: 'a, F: FnOnce() -> &'a mut T>(_: F) { }

fn main() {
    let mut slot = Box::new(1);
    let closure = || &mut slot; // why doesn't the compiler make this `FnOnce` only?
    fn_once(closure);
}

(Playground)

The compiler could (theoretically) reason that closure can't implement FnMut, couldn't it?


I guess that's what's written there (your link):

In order to infer FnMut vs. FnOnce we would have to know if the closure returns a reference into itself. I am not very experienced with the design of the compiler, but from what I gather, this information is not available until borrowck, which itself relies on type checking to be done first. This might be difficult to solve.

1 Like

I've edited my previous comment and added the relevant argument: if the determination that FnMut is impossible is determined by "trying borrowck", that'd go a bit against current principles of borrow-checking not influencing types and behavior and the like.

If the compiler can argue based on the usage of the closure within main that FnMut is unnecessary (regardless of whether it would be possible) that does (to me, right now) seem like a better approach to improve the situation.

So I would summarize it as:

It's (sort of) intentional for now, because these "downgrades" are sometimes needed (as of yet).

I'll have to think about what I do. Maybe remove the bound (and require type annotations for the closure's arguments), or just put an Fn bound, even though not necessary.

Thanks for all the input. I learned a lot about closures again.

Oh, sorry, it was just speculation that it’s related to rustc not assuming some trait bounds that are always true, leading to having to write redundant bounds, but this is not that after all. (Rust of course does normally add implicit supertrait bounds but I thought this might be a corner case.)

1 Like