Is this write-up of closure type inference correct?

Hi, I'm studying closures. Would you say this writeup is about correct? My test code.

if (you "carelessly" "destroy" any of the variables on parent stack by moving content out of them) {
    you get FnOnce
    those variables are moved to the closure on creation

    if (you also use "move" keyword) {
        all accessed variables from the parent frame not mentioned above which are not "Copy" are also moved to the closure
        all accessed variables from the parent frame not mentioned above which are "Copy" are copied to the closure
    } else {
        all accessed variables from the parent frame not mentioned above are referenced by the closure via & and &mut
    }
} else {
    if (use "move" keyword) {
        all accessed variables from the parent frame which are not "Copy" are moved to the closure
        and accessed variables from the parent frame which are "Copy" are copied to the closure
    } else {
        all accessed variables from the parent frame are referenced by the closure via & and &mut
    }
    
    if (you modify any variables on/from parent stack frame) {
        you get FnMut
    } else {
        you get Fn
    }
}

I think you're correct, but I believe the trait vs. capture "style" can be a little more independent than how you've written it:

// trait:
if (some non-Copy variable is used by value) {
    FnOnce
} else if (some variable is modified (i.e. used by &mut reference)) {
    FnMut
} else { // all variables are used by & reference
    Fn
}

// capture "style":
for variable in parent_variables {
     if ("move" keyword on closure) || (`variable` is used by value) {
         `variable` is captured by value
     } else (`variable` is used by `&mut` reference) {
         `variable` is captured by `&mut` reference
     } else { // only used by & reference
         `variable` is captured by `&` reference
     }
}

The loop could also be written as:

if ("move" keyword) {
    all variables are captured by value
} else {
    each variable is captured by value, by &mut reference or by & reference, matching how it is used
}

NB. I've mostly folded non-Copy and Copy types together, by just talking about being used by value or by reference. A Copy-copy and a move are essentially the same thing, both occur when using something by-value, e.g. foo(a) or b = a are both using a by value. The only difference is the source (a) can continue to be used after that for Copy types, and the same applies to closure-captures.

Incidentally, I've done some writing on both closures and moves/copies, which others have apparently found helpful, so you may be interested too:

2 Likes

Great explanation, thx a lot!

not a big deal but it seems that when I use i32 by value it's still captured as &i32:
In this example the error message reveals closure "type" as

    [closure@<anon>:32:21: 38:10 x:&mut i32, v:&Box<i32>, a:&i32]

even though 'a : i32' is only used as 'let _ = a;'

Was it perhaps done to ensure 'a' is not modified while the closure exists?

Oh, whoops. I realised that after writing, and corrected the trait inference (FnOnce only applies for non-Copy variables), to be clear it should read ("move" keyword on closure) || (`variable` is not `Copy` and used by value).

The reason is Copy types can be copied out to be by-value from behind &T references, i.e. let _ = a; is the same as let _ = *(&a);: a use by-&-reference.