2

As explained in Why is the move keyword needed when returning a closure which captures a Copy type? and How to copy instead of borrow an i64 into a closure in Rust?, if a closure captures a type that implements Copy, we need to use the move keyword to eagerly copy the value:

fn use_move_to_copy_into_closure() -> impl FnOnce(i32) -> bool {
    let captured = 0;
    move |value| value > captured
}

As of Rust 2021, disjoint capture in closures means that only the specific fields used in a closure are captured by the closure:

struct Wrapper(i32);

fn edition_2021_only_captures_specific_fields(captured: Wrapper) -> impl FnOnce(i32) -> bool {
    let ret = move |value| value > captured.0;
    drop(captured); // fails in 2015, 2018, succeeds in 2021
    ret
}

If I capture a Copy field belonging to a reference, however, the field is not copied:

struct Wrapper(i32);

fn capturing_a_field_of_a_reference(captured: &Wrapper) -> impl FnOnce(i32) -> bool {
    move |value| value > captured.0 
}
error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
  --> src/lib.rs:15:60
   |
15 | fn capturing_a_field_of_a_reference(captured: &Wrapper) -> impl FnOnce(i32) -> bool {
   |                                               --------     ^^^^^^^^^^^^^^^^^^^^^^^^
   |                                               |
   |                                               hidden type `[closure@src/lib.rs:16:5: 16:36]` captures the anonymous lifetime defined here
   |
help: to declare that the `impl Trait` captures `'_`, you can add an explicit `'_` lifetime bound
   |
15 | fn capturing_a_field_of_a_reference(captured: &Wrapper) -> impl FnOnce(i32) -> bool + '_ {
   |                                                                                     ++++

I expected that the field .0 would be copied in, just like in the unwrapped i32 or the owned Wrapper cases. What causes this difference?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

2 Answers2

0

move captures the environment by value, but things in the environment that are references remain references -- the references are captured by value.

Put another way, move closures only try to move values, because otherwise there wouldn't be a way to simultaneously capture some things by value and some things by reference. For example, it's a common pattern to do this when dealing with threads in e.g. crossbeam. Assume these structs:

#[derive(Clone)]
struct Foo;

impl Foo {
    pub fn baz(&mut self, _: i32) {}
}

struct Bar(pub i32);

And this snippet:

let foo = Foo;
let bar = Bar(0);

{
    let mut foo = foo.clone();
    let bar = &bar;
    s.spawn(move |_| {
        foo.baz(bar.0);
    });
}

Here the closure takes ownership of the clone of foo but references bar.0. This is true even if the type of bar.0 is Copy.

If it didn't work this way, there would be no way to express that the closure should own the foo clone, but borrow the copyable value bar.0.

Remember that implementing Copy on a type only means that an attempt to move a value of that type will instead copy. Since captured is a reference in your third example, capturing captured.0 doesn't attempt to move the i32 like it does in your second example where you have an owned value, and if a move isn't attempted then no copy can happen.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • The whole section about *otherwise there wouldn't be a way to simultaneously capture some things by value* is true, but not obviously relevant to the specifics around disjoint closure captures. Can you cite an authoritative source that discusses that a capture *of a field belonging to a reference* should behave as if it were itself a reference, as opposed to behaving as if it were a value? – Shepmaster Apr 06 '22 at 18:58
  • @Shepmaster I've never seen anything resembling a specification for all of Rust, so I'm not sure there _is_ much in the way of an "authoritative source." I've looked at both the disjoint capture section of the 2021 guide and the RFC, and none of the examples I saw there gave any impression that capturing a field by reference could copy the value of that field _implicitly._ Disjoint capture only deals with _which parts of values/borrows_ get captured, not directly _how_ they get captured. – cdhowie Apr 06 '22 at 19:07
  • @Shepmaster I'm extrapolating from the [`move` keyword](https://doc.rust-lang.org/std/keyword.move.html) docs: "Capture a closure’s environment by value." To me this means the whole of the environment is in the closure as it is outside of the closure. References outside of the closure don't _just become_ values inside of the closure. – cdhowie Apr 06 '22 at 19:08
  • s/capturing a field by reference/capturing a field through a reference/ – cdhowie Apr 06 '22 at 19:13
  • [This](https://doc.rust-lang.org/reference/expressions/closure-expr.html) is the closest thing I can think of to an authoritative source, and the text describing when values are moved/copied/referenced is vague. I think the only authoritative source we might actually get on this topic is a Rust developer. – cdhowie Apr 06 '22 at 19:27
-1

It's not a question of whether the copy occurs, but when it occurs. The copy only happens when the closure is called, not when it's defined (how could it be -- a captured hasn't been provided yet). Since the closure outlives the function call, it needs to know that its reference to captured will be valid when it's actually called precisely so that it has something to copy. Therefore it needs to be annotated with a lifetime tying it to the lifetime of captured. Thanks to lifetime inference, it's sufficient to simply add '_ to the impl instead of needing to explicitly write fn capturing_a_field_of_a_reference<'a>(captured: &'a Wrapper) -> impl 'a + FnOnce(i32) -> bool.

BallpointBen
  • 9,406
  • 1
  • 32
  • 62
  • This appears to be the same content as the answers to [Why is the move keyword needed when returning a closure which captures a Copy type?](https://stackoverflow.com/q/71767966/155423) — are you suggesting it's a duplicate? However, the code in this question *does* use the `move` keyword, so it's unclear how it would apply. *a `captured` hasn't been provided yet* — what do you mean? `captured` is an argument to the functions, not to the closure. It's provided equally in `edition_2021_only_captures_specific_fields` and `capturing_a_field_of_a_reference`. – Shepmaster Apr 06 '22 at 17:43