Capturing of `Copy` types in closures

The following code compiles:

fn take<T>(a: T) {
    drop(a);
}

fn process<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn main() {
    let non_copy_type = vec![12, 14, 18];
    let copy_type = 12_i32;

    process(|| {
        take(copy_type);
        take(non_copy_type);
    });
}

from this I conclude that both clone_type and copy_type are captured by value. And that is what I would expect. However, the following doesn't compile (note that I've only added 'static bound):

fn take<T>(a: T) {
    drop(a);
}

fn process<F>(f: F)
where
    F: FnOnce() + 'static,
{
    f();
}

fn main() {
    let non_copy_type = vec![12, 14, 18];
    let copy_type = 12_i32;

    process(|| {
        take(non_copy_type);
        // take(copy_type);               // compiler reports error
    });
}

What is happening here? I expected that both variables are still captured by value but it seems that copy_type is captured by reference

Did you make a copy & paste error somewhere? process() takes an F: FnOnce() + 'static, and neither non_copy_type nor copy_type implements FnOnce.

I'm going to assume this is the error you meant to ask about. The compiler will capture Copy types by reference, because it prefers not to move things (without the move qualifier) if it has a choice. (Since it can create a copy from a reference, it captures a reference.)

Here's the relevant part of the reference (though it is incomplete in at least some ways).

1 Like

yes, fixed now

The compiler prefers to capture a closed-over variable by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally by move

I was aware of this and this makes sense

It will pick the first choice of these that is compatible with how the captured variable is used inside the closure body

but am I not using my variable by value? and why is there a difference in behavior between non-copy and copy types? and why does it depend on the 'static bound being present?

Yes, but because it's Copy, it can be captured by reference and the uses in the closure turn into *capture or such. Whether this is the best default or not could be debated,[1] but that's how it works.

The above approach relies on Copy; Rust doesn't "like" implicit clones, which is what would be required to do the analogous thing for Clone but not Copy types. (Some people do want a "capture a clone" mode or similar for things like Arc<_>.)

It captures a reference when there's not a 'static bound too. The captured reference can't be 'static because it's referencing a local variable. That's why adding 'static results in an error. Nothing changed about what got captured.


  1. it avoids copying large Copy types but also leads to the error under discussion ↩ī¸Ž

turn into *capture or such

this explains it, weird as it is. Thanks