FnOnce does not move captured variable

Hi,

I wrote some code to test FnOnce capturing, it seems the code below does not follow the rule.
Do I misunderstand it?

struct xx<T: FnOnce() -> *mut i64> {
    f: T
}

    if let Some(T) = v1_iter.next() {
        let f1 = xx {
            f: || -> *mut i64 {*T = *T + 20; T}
        };
        (f1.f)();
        println!("{:?}", T); // it is OK to print the moved T
       (f1.f)(); // BUT the f1.f is moved, which is not captured
    }

Add move before ||, otherwise it'll only capture the variables as references.

You need three backticks to make a code block, not one.

```
// your code
```

thanks, adding the move, it is OK for println!("{:?}", T); But the other question still exists: why f1.f is moved after a single call (f1.f)();

And, does the move keyword take effect on FnOnce only? because the following code is OK even I add the move keyword before ||

struct xx<T: FnMut() -> *mut i64> {
    f: T
}

fn iterator_demonstration() -> ()
{
    let mut v1 = vec![1, 2, 3];
    let mut v1_iter = v1.iter_mut();
    assert_eq!(v1_iter.next(), Some(&mut 1));
    assert_eq!(v1_iter.next(), Some(&mut 2));

    if let Some(T) = v1_iter.next() {
        let mut f1 = xx {
            f: move || -> *mut i64 {*T = *T + 20; T}
        };
        (f1.f)();
        (f1.f)();
        //println!("{:?}", T);
       // (f1.f)();
    }

    let v1_iter = v1_iter.next().map(|x: &mut i64| -> *mut i64 {*x = *x + 2; x});
    if let Some(T) = v1_iter {
        unsafe {
            *T = *T + 100;
        }
    }
    println!("{:?}", v1)
}

Ok, I figured out.
It is because the capture happens at definition level, not calling level, the following code can't be compiled:

fn iterator_demonstration() -> ()
{
    let mut v1 = vec![1, 2, 3];
    let mut v1_iter = v1.iter_mut();
    assert_eq!(v1_iter.next(), Some(&mut 1));
    assert_eq!(v1_iter.next(), Some(&mut 2));

    if let Some(T) = v1_iter.next() {
        let mut f1 = xx {
            f: move || -> *mut i64 {*T = *T + 20; T}
        };
        (f1.f)();
        (f1.f)();

        let mut f2 = xx {
            f: move || -> *mut i64 {*T = *T + 20; T}
        };

        (f2.f)();

        //println!("{:?}", T);
       // (f1.f)();
    }
}

Because according to the definition of struct xx, f is FnOnce, so you can only call it once.

1 Like

Not exactly. A FnOnce is known to be callable once, in the sense of at least once. If you don't know anything else about the callable, then this least / lower bound becomes also an upper bound, as with many abstractions, so it effectively becomes an at most once.

In the OP example however, the xx struct does contain a fully visible F field, so when F = some Fn closure : Fn() : FnOnce(), the FnOnce bound is met but the additional information can allow multiple calls.

To see this, consider the following simpler example:

fn fn_once_identity<T> (x: T) -> T
where
    T : FnOnce(),
{
    x
}

let f = fn_once_identity(|| {
    println!("Hello, World!");
});
f();
f();
  • despite the FnOnce bound, the returned value is still identical to the input value, so it keeps all its properties, such as it being Fn() on top of FnOnce.

  • Playground

So an FnOnce bound is only limiting when that is all the information we have about some unspecified type (e.g., a generic type or an impl FnOnce() type):

fn call_twice<F : FnOnce()> (f: F)
{
    f();
    f(); // ~ERROR: use of moved value
}

fn fn_once_eraser (f: impl FnOnce()) -> impl FnOnce() { f }

let f = fn_once_eraser(|| {});
f();
f(); // ~ERROR: use of moved value
5 Likes

Nice insight with the fn_once_identity! But yeah, since OP is calling it "through" type xx, the only information about the function is that it implements FnOnce.

Edit: ah no, now I'm confused. Why is the type of f1.f not deduced to be something that actually implements more than FnOnce? Shouldn't it be analogous to the fn_once_identity situation? Playground to follow along: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=90e27255e7c27702206108b594608686

1 Like

Now I'm confused too :sweat_smile:


Ok, after some testing, I think it has to do with the FnOnce bound interacting with type inference and the compiler-generated closure type: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=be0962d751b4b705c14f347b24eb2e91

1 Like