Selectively move values into a closure

fn main() {
    let mut s1 = String::from("Quis sit tempor");
    let s2 = String::from("Duis consectetur nulla");
    let s3 = String::from("Et adipisicing");

    let closure = {
        let (s1, s2) = (&mut s1, &s2);
        move || format!("{}, {}, {}", s1, s2, s3)
    };

    dbg!(closure());
}

Is there any alternative for this?

4 Likes

If it’s only a FnOnce closure then using the value by moving it will make it be moved into the closure, too (unless it’s a Copy type). I.e. use e.g. a block expression {s3}, or an assignment let s3 = s3; inside of the closure. If it’s supposed to still be FnMut, then I believe your approach is already the best one, since the approach described in the previous sentence would downgrade the closure to be only FnOnce.

Your example code is a bit too much toy example for me to be 100% certain I understood your question correctly. I’m fairly confident, but if the answer seems weird to you or you have follow-up questions anyways, feel free to provide more context and/or ask further questions.

1 Like

How you've written it is generally how its done, there is no special syntax or anything to tell which variables should be moved.

I see the closure crate exists which might help remove some boilerplate for this.

let closure = closure! {
    ref mut s1, ref s2, move s3,
    || format!("{}, {}, {}", s1, s2, s3)
};
1 Like

I usually just create references in the same indentation level. I think non-lexical lifetimes make this easy:

fn main() {
    let mut s1 = String::from("Quis sit tempor");
    let s2 = String::from("Duis consectetur nulla");
    let s3 = String::from("Et adipisicing");
    
    let s1_ref = &mut s1;
    let s2_ref = &s2;
    let closure = move || format!("{}, {}, {}", s1_ref, s2_ref, s3);

    dbg!(closure());
    
    // `s2_ref` is `Copy`, so can we do this?
    drop(s2); // Yes, we can, because of NLL!
    //closure(); // Unless we do something like this.
}

(Playground)

1 Like

Closures try to automatically infer the least restrictive capture kind which can be used with their bodies. If something inside of the closure requires the value to be moved, it will be captured by move. If all uses of a capture require only &mut, it will be captured by ref mut, similarly for & and ref captures. This means that often you don't need to think about the closure capture modes, and when you do, you may just insert a dummy move to hint the automatic inference. Here is your example:

fn main() {
    let mut s1 = String::from("Quis sit tempor");
    let s2 = String::from("Duis consectetur nulla");
    let s3 = String::from("Et adipisicing");

    let closure = || {
        // A dummy move of `s3` causes it to be captured by move.
        let s3 = s3;
        format!("{}, {}, {}", s1, s2, s3)
    };

    dbg!(closure());
    // These values weren't moved.
    dbg!(s1, s2);
    // But `s3` was! Uncommenting this will cause an error.
    // dbg!(s3);
}

For this reason move keyword is generally used when you want all captures to be moved, like when returning a closure from a function. Note that sometimes the algorithm may behave in unexpected ways to to unexpected constraints. For example, format! macro always captures all passed parameters by reference. This means that even if you try to format a &str, it will be captured by ref, i.e. as &&str, thus necessitating an explicit move.

fn move_it(s: &str) -> impl FnOnce() -> String + '_ {
    // This doesn't compile, since `s` is always captured by ref. 
    // return || format!("{}", s);
    // But this compiles, since we move the captured reference inside.
    move || format!("{}", s)
}

A similar algorithm applies to async { } blocks, which can also capture external parameters by move, ref or ref mut. However, in that case the algorithm is quite flaky, which means that the let x = x; trick above usually won't work. This leaves a full move and explicit clones/reborrows, as in your question, the only way to deal with captures of async blocks in many cases.

1 Like

I just figured that if you use the variable only in one place within the closure, you might also do the following:

use std::convert::identity as mov;

fn main() {
    let s1 = String::from("Quis sit tempor");
    let s2 = String::from("Duis consectetur nulla");
    let s3 = String::from("Et adipisicing");

    let closure = || {
        format!("{}, {}, {}", s1, s2, mov(s3))
    };

    dbg!(closure());
    // These values weren't moved.
    dbg!(s1, s2);
    // But `s3` was! Uncommenting this will cause an error.
    // dbg!(s3);
}

(Playground)

But I think the let s3 = s3; is the better way to go, especially as using the identity function can only be used once for a variable:

use std::convert::identity as mov;

fn main() {
    let s = "Hello".to_string();
    println!("{}", mov(s));
    println!("{}", mov(s)); // won't work
}

(Playground)

No need for the funny looking mov either, as I stated above:

3 Likes

Oh I missed that, thanks.

And so we've come full circle back to std::move.

That's a nice trick, but I feel it's even more obscure that let x = x;. At least the latter is immediately visible, and it's intuitively obvious that it moves the value. With { x }, I wouldn't immediately remember that it always moves x, and it's easier to miss the braces.

I think something like move(x) would be better - a canonical explicit intuitively obvious way to move a value. It doesn't fit much in current Rust, though. It also doesn't solve the problem of cloning the capture before a move.

1 Like

Well… except that C++ doesn’t have any move operation, and std::move is closer to mem::take in Rust (except that they don’t specify what’s exactly the value the “moved” value is left in, so it can – for example – just leave it untouched in cases where types are Copy so to speak, I suppose).[1]


  1. This all is base on my – limited – understanding of C++. Someone more knowledgeable please correct me if I’m wrong. ↩︎

1 Like

I’ve seen block expressions used for this effect often enough that it doesn’t feel obscure to me anymore, but it’s a valid point, I guess :slight_smile:

I sometimes feel like what people want to be able to do is just specify the way the captured value is preprocessed at the use site. Currently it needs to be outside of the closure body (and sometimes a block can be nice to avoid the renaming of the variables,

{ let foo = foo.clone(); |…| { usage_of(foo) } }

but I’ve often enough seen people try something like

|…| { usage_of(foo.clone()) }

so I’m wondering whether some syntax for “execute this part early while constructing the closure” could be feasible

|…| { usage_of(⟦foo.clone()⟧) }

Of course, in nested cases, it’s hard to tell what closure this thing actually belongs to (and the syntax is temporary), so it might be somewhat infeasible.

Note that using a "non-move" closure and use let x = x to make the move in the closure will not actually make a "move capture", the move will only occur inside the closure and x is still captured by reference.

It may have an impact for closures expected to be 'static (like in thread::spawn). You won't be able to actually move a temporary variable into the closure as the reference capture will not be 'static.

I guess it shouldn't be really problematic because this construct is for capturing some variables by move and some others by reference. It is unlikely to be usable for a 'static closure in any other way. But just wanted to point that fact.

The most generic way to capture by move and reference and don't have to think about special cases, is still to use move || and actually wrap variables expected to be captured by reference in a reference outside the closure.

I remember some problems like that, but I couldn't reproduce it. In the following example it does work to move arc2 into the inner closue, even if both closures are non-move:

use std::sync::{Arc, Mutex};
use std::thread::spawn;

fn main() {
    let arc1 = Arc::new(Mutex::new(5));
    let arc2 = arc1.clone();
    (|| {
        spawn(|| {
            let arc2 = arc2;
            *arc2.lock().unwrap() += 10;
        }).join().unwrap();
    })();
    assert_eq!(Arc::try_unwrap(arc1).unwrap().into_inner().unwrap(), 15);
}

(Playground)

So can you explain which exact problem you are referring to?

It does not work for moving T: Copy (so just copying)

use std::thread::spawn;

fn main() {
    let duration = std::time::Duration::from_secs(5);
    spawn(|| {
        let duration = duration;
    });
}
error[E0373]: closure may outlive the current function, but it borrows `duration`, which is owned by the current function
 --> src/main.rs:5:11
  |
5 |     spawn(|| {
  |           ^^ may outlive borrowed value `duration`
6 |             let duration = duration;
  |                            -------- `duration` is borrowed here
  |
note: function requires argument type to outlive `'static`

(Playground)

Oh, that problem sounds familiar :confounded:. And neither std::convert::identity nor { … } can help.

Why does Rust not copy here? Isn't a copy cheap? Probably references are cheaper for some Copy types? Couldn't Rust determine that the value needs to be copied in order to fulfil the lifetime requirement?

The rationale is probably that copying only needs read-only access, so shared reference is sufficient, and closures always capture in the “weakest” for possible.

On second thought, the more important reason is the following:

With Copy types, the type can still be accessed after the closure is created. So you code like

fn main() {
    let mut n = 1;
    (0..3).for_each(|_| {
        println!("{}", n * 10); // access by-value, copies `n`
        n += 1;
    });
    println!("now n is {n}");
}

to work as expected, printing

10
20
30
now n is 4

whereas using a move closure will change the behavior to

10
20
30
now n is 1

(which IMO in and by itself is somewhat questionable too that it does this without any warning, but most definitely should not become the default behavior for non-move closures, too)


Yeah, there’s certainly an upper bound for cost of using a reference, whereas Copy types can be arbitrarily expensive.

The borrow checker is just that, a checker. It deliberately never influences behavior. This means that projects like rust-gcc can get away with not having a borrow checker whilst still being useful. More importantly, this means that the user doesn’t need to be able to do a full borrow-checking pass in their head in order to determine which way their closures capture their variables. Also: the borrow checker can get smarter over time without such improvements leading to breaking changes. I.e. the question

Q: are there problematic lifetime requirements in this code that could be solved by making this closure move its capture

– in particular the first part “are there problematic lifetime requirements in this code” – could be answered with “yes” today, and with “no” in the future for code that only newer borrow checking algorithms, e.g. the things that polonius does, can accept. This means that any rule like

If question Q above is answered “yes” then change the closure capture behavior

can result in behavioral changes, and thus breakage: maybe not the most realistic scenario, especially for a Copy type, but…: e.g. capturing by-value in a particular case could’ve not only solved lifetime issues but also solved a closure fulfilling Send whilst capturing a non-Sync type. If e.g. polonius could solve the lifetime issue, and the behavior changed to capture-by-reference, then the Send bound would be broken. Editions also wouldn’t help, unless we want to keep old versions of the borrow checker around indefinitely (and we definitely do not want that), in order to ensure unchanged behavior in older editions.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.