Explicit lifetime required in closure

Oh wait a second... this changes everything!

I know you or someone else mentioned that above, but it feels weird, because I keep associating brackets with scope and things being dropped when the brackets finish...

Got to remember that closure brackets do not mean scope like they do elsewhere, the stuff moved into closures only gets dropped when the closure itself is dropped... okay, trying to burn that into my brain :smiley:

Hmmm not sure I 100% get that

Due to haw format_args gets expanded, it only takes a reference to its arguments. Because that is the only thing the closure does, the closure also only requires a reference.

Oh... this is getting into the whole Fn, FnOnce stuff?

i.e. if it's FnOnce then foo would be dropped when the closure finishes?

Yes it is getting into that.

If it is Fn or FnMut, then nothing gets dropped at the end of the call. If it is only FnOnce, then it will be dropped at the end of the call.

1 Like

Is there a way of determining which it is? i.e. I wouldn't have thought to go look at the docs for format_args

I usually don't check, just add move and Arc until the borrow checker stops complaining :slight_smile:

1 Like

If you really want to check you can create a generic function with the bound that you want to check and then try to call that function. The function doesn't need to have any arguments and can have an empty body.

fn check_is_Fn<F: Fn() -> usize>(_: F) {}

check_is_Fn(|| 0); // this compiles, so it is fine
let v = vec![];

// this does not compile, so it is not a Fn() -> usize
check_is_Fn(|| {
    drop(v);
    0
});
2 Likes

Alrighty I think it's starting to click... is the following accurate?

move is evaluated after macros.

Therefore, if the only "owned" values in a closure are those passed to macros like format! or println!, then because those macros re-write the code to only take references - the closure will not need to actually move anything and therefore it remains Fn/FnMut.

If, however, a closure with move truly consumes owned values, then it is FnOnce

?

For example, this is fine (i.e. format_cl retains the Fn trait)

let a = String::from("a");
let format_cl = move || {
    format!("before: {}, after: {}", a, a.to_uppercase())
};

let b = format_cl();
let c = format_cl();

While this is not (i.e. format_cl becomes FnOnce only):

let a = String::from("a");
let format_cl = move || {
    let foo = a; //<-- genuinely forces `a` to be moved in here
    format!("before: {}, after: {}", foo, foo.to_uppercase())
};

let b = format_cl();
let c = format_cl();

The reason is that conceptually this:

format!("before: {}, after: {}", a, a.to_uppercase())

is really this (the actual macro-rewriting is more complicated, but it's the idea):

do_stuff("before: {}, after: {}", &a, &a.to_uppercase())

And so the first example never really uses a as an owned value - only borrows it

Wheras in the latter example a is actually moved, and so the closure can only be called once

Oh interesting... this is legal too...

I assume because to_uppercase() only needs a as &Self, i.e. it's the same idea as above?

fn main() {
    let a = String::from("a");
    
    let format_cl = move || {
        a.to_uppercase();
    };
    
    format_cl();
    format_cl();
}

Closer, macros are always evaluated first, before anything else is done. In the macro expansion of println, it takes a reference to everything it prints. Therefore nothing is moved out of the anonymous struct that holds the environment in in closure body. Because of this it is at least FnMut, because nothing is mutably borrowed from the anonomous closure, it is Fn.

1 Like

Yes

1 Like

Woohoo!!! Next step for me to feel more confident is probably to think of closures as sugar - i.e. to connect this:

With what you wrote above. Will go back and take a look :wink:

Thanks again!

2 Likes