Fn traits, call and call_once mechanism (ignoring call_mut for simplicity)

I think I mostly understand the Fn traits.

But based on that understanding I would expect a function that takes a closure with trait bound FnOnce to consume any captured variables, since only call_once(self...) should be available.
call(&self...) should not be available as Fn is only a subtrait of FnOnce.

However in practice if you call such a function with a closure that implements the Fn trait then it does not consume its captured variables.

This is good, as you don't want to consume variables unnecessarily but seems contradictory of the Fn trait hierarchy.

I'm probably misunderstanding how closures work at a lower level.
Can call(&self...) be used in a function with trait bound FnOnce?
Or maybe call_once(self..) is being used but doesn't do what I expect?

There are several different features which can engage to make closures work smoothly.

Can call(&self...) be used in a function with trait bound FnOnce?

Yes! Because this impl exists:

impl<A, F> FnOnce<A> for &F
where
    A: Tuple,
    F: Fn<A> + ?Sized,

If you have an &impl Fn you can use that reference as a FnOnce. However, if there isn’t any code that specifically takes a reference, then this doesn’t apply.

Or maybe call_once(self..) is being used but doesn't do what I expect?

Maybe!

If possible, closures implement Copy. Therefore, the closure might be being used as FnOnce, but copied when that happens.

By default, closures prefer to capture variables by reference. If all variables are in fact captured by immutable reference, then the closure will implement Copy since all of its captures are Copy. Therefore, it can be copied when it is used as FnOnce.

1 Like

Thanks!

Useful for me to click through to the source from your link, to see call being used:

impl<A: Tuple, F: ?Sized> FnOnce<A> for &F
where F: Fn<A>,
{
    type Output = F::Output;
    extern "rust-call" fn call_once(self, args: A) -> F::Output {
        (*self).call(args)
    }
}

Within a function body with (only) a FnOnce bound, the function can only call call_once which takes the generic type that meets the bound by value.[1] That will consume the value from the function body's POV, unless the type is known by the function to implement Copy as well.[2] Note that this works the same as any other self-taking method.

That's orthogonal to whether a/the closure captured everything by value or not, though.

The method below doesn't consume the T either.

struct Capture<T> {
    field: T,
}

impl<T> Capture<T> {
    fn method(self) {}
}

fn main() {
    let s = "hello".to_owned();
    let capture = Capture { field: &s };
    capture.method();
    println!("{s}");
}

Incidentally, if you define a closure directly in a context with a FnOnce bound -- like as an argument to a function with such a bound -- it will[3] override some parts of the compiler's inference algorithm; in particular it will only implement FnOnce for that closure, and won't attempt FnMut or Fn. But I think it still does not effect how captures are inferred. (And if all captures are Copy -- like if they're all shared references -- the closure will also be Copy, so you may still be able to call it more than once.)


  1. Normally I would say "takes the closure by value", but the distinction is relevant to this conversation. ↩︎

  2. due to an bound, because the bound was for<'a> &'a F: FnOnce(..), ... ↩︎

  3. currently at least ↩︎

1 Like

Thanks!