4

I am storing a closure in a struct like this:

#[derive(Clone)]
struct S<'a> {
    func: &'a FnOnce() -> u32
}

fn main() {
    let s = S { func: &|| 0 };
    let val = (s.func)();
    println!("{}", val);
}

When I compile, s.func cannot be moved to execute itself. I understand why it cannot be moved (namely that it's only a reference and that its size is not known at compile time), but don't know why it's being moved at all -- is it just because closures are implemented via traits?

Here's the error message:

error[E0161]: cannot move a value of type std::ops::FnOnce() -> u32:
the size of std::ops::FnOnce() -> u32 cannot be statically determined
 --> main.rs:8:15
  |
8 |     let val = (s.func)();
  |               ^^^^^^^^

error[E0507]: cannot move out of borrowed content
 --> main.rs:8:15
  |
8 |     let val = (s.func)();
  |               ^^^^^^^^ cannot move out of borrowed content

error: aborting due to 2 previous errors

Is this only way the solve this to store the closure on the heap (via Box<FnOnce() -> u32>)? And why does calling a closure move it? Presumably calling it doesn't mutate the function itself.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
cderwin
  • 425
  • 4
  • 11

2 Answers2

6

The closure is being moved because FnOnce::call_once takes self by value. This contract enforces the guarantee that the function will not be called more than once.

If you will indeed be calling the closure at most once, and you want to use the FnOnce trait, then your struct needs to take ownership of that closure (and you will need to make your struct generic on the closure type). Note that calling the closure will move the closure out of your struct, thereby invalidating the whole struct; you may work around that by wrapping the FnOnce in an Option and take-ing the closure out of the Option before calling it.

If you might be calling the closure more than once, you don't want to take ownership of the closure, or you don't want to make your struct generic on the closure type, then you should use either Fn or FnMut instead. Fn::call takes self by reference and FnMut::call_mut takes self by mutable reference. Since both accept references, you can use trait objects with them.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • This helps loads, thanks. I ended up using `Fn` instead of `FnOnce` because I wanted to create a const instance of the struct, but this whole response helps a ton with understanding the whole thing. – cderwin Dec 05 '16 at 00:18
2

As explained by Francis, declaring a closure FnOnce tells Rust that you accept the broadest class of closures, including those that exhaust the objects they capture. That such closures are invoked only once is ensured by the compiler by destroying the closure object itself (by moving it into its own call method) when invoked.

It is possible to use FnOnce and still not have S generic on the closure, but it requires some work to set things up so that the closure can't be possibly invoked more than once:

  • the closure must be stored in an Option, so its contents can be "stolen" and the Option replaced with None (this part ensures that the closure won't be called twice);
  • invent a trait that knows how to steal the closure from the option and invoke it (or do something else if the closure was already stolen);
  • store a reference to the trait object in S - this enables the same S type works on different closures without being generic on closure type.

The result looks like this:

trait Callable {
    fn call_once_safe(&mut self, default: u32) -> u32;
}

impl<F: FnOnce() -> u32> Callable for Option<F> {
    fn call_once_safe(&mut self, default: u32) -> u32 {
        if let Some(func) = self.take() {
            func()
        } else {
            default
        }
    }
}

struct S<'a> {
    func: &'a mut Callable
}

impl<'a> S<'a> {
    pub fn invoke(&mut self) -> u32 {
        self.func.call_once_safe(1)
    }
}

fn main() {
    let mut s = S { func: &mut Some(|| 0) };
    let val1 = s.invoke();
    let val2 = s.invoke();
    println!("{} {}", val1, val2);
}

The only place that knows details about the closure is the implementation of Callable for the particular Option<F>, generated for each closure and referenced by the vtable of the &mut Callable fat pointer created when initializing the func in S.

user4815162342
  • 141,790
  • 18
  • 296
  • 355