Error about capturing when compiling function closure

 fn test() {
      let mut a = [4];
      let mut b = [3];
      let mut c = &mut a[..];
      let mut d = &mut b[..];
  
      let _ = || {
          d = c; // `d` declared here, outside of the closure body
      };
  }

I have thought for half a day, but still not find the reason, why?

I think the error message is pretty self-explanatory:

error[E0585]: found a documentation comment that doesn't document anything
  --> src/lib.rs:10:20
   |
10 |             d = c; /// `d` declared here, outside of the closure body
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: doc comments must come before what they document, if a comment was intended use `//`

There's nothing I can really add that isn't already directly stated above.

If we remove this comment, the error seems to be what was asked about (playground):

error[E0521]: borrowed data escapes outside of closure
 --> src/lib.rs:9:9
  |
6 |     let mut d = &mut b[..];
  |         ----- `d` declared here, outside of the closure body
...
9 |         d = c;
  |         ^^^^^

You can't make variable c uninitialized by moving out the instance it has while inside a closure.
Replacing the instance fixes it.

d = std::mem::replace(&mut c, &mut[]);
2 Likes

The problem isn't about assigning c alone, as this working example demonstrates.

    let _ = || {
        let mut d = d;
        d = c;
    };

Also note that d = c is actually reborrowing the c and not moving it,[1] which is why this program also works:

    let mut c = &mut a[..];
    let mut d = &mut b[..];
    d = c;
    d = c;

So what is the problem? One component is that the compiler is deciding that the original closure should be a FnMut() closure that can be called multiple times. If you force it to be a FnOnce() closure, it compiles.

One could then ask why FnMut() is problematic, and it basically comes down to this scenario.[2] You can't reborrow a &'short mut &'long mut _ for &'long mut in order to make the assignment (and FnMut() closures only support arbitrarily short, non-escaping borrows of the closure[3]).


  1. when assignment of a &mut is a reborrow and when it is a move is somewhat subtle ↩︎

  2. The example corresponds to a move closure, but isn't all that different for a non-move closure; note also how move doesn't fix the OP. ↩︎

  3. and changing the signature to &'a mut self makes it only callable once to boot ↩︎

4 Likes

Thanks for all your replies, that really can solve the problem.

fn main() {
    let mut a = [2];
    let mut c = Option::<&mut[i32]>::None; 

    let _f = || {
        a[0] = 3;
        c.insert(&mut a[..]); // Not work
        // c = Some(&mut a[..]); // Not work
        // c.replace(&mut a[..]); // Not work
        // std::mem::replace(&mut c, Some(&mut a[..])); // Not work
    };
}

rust-play
Above is another code a little different with previous code. And I try all the methods, all outputs the same error :

error[E0521]: borrowed data escapes outside of closure
 --> src\main.rs:8:9
  |
4 |     let mut c = Option::<&mut[i32]>::None; 
  |         ----- `c` declared here, outside of the closure body
...
8 |         c.insert(&mut a[..]); // Not work
  |         ^^^^^^^^^^^^^^^^^^^^

Does a FnOnce() not work for your use case?

(If it does work, there might be an alternative way to prompt the compiler to make the closure a FnOnce() closure; I just did a minimal test.)

Edit: e.g.

1 Like

A smart and elegant solution to this kind of problems :100:, and does solve it. Here, what still confusing me is why the code I input does not work? If the variable a a[0]=3 in body is borrowed from a outside of the closure body, its lifetime &mut a[..] is valid within the scope of variable a defined. According to that, the error should not happen. How do we explain it?

Borrows of a and c are captured when the closure is constructed, not when it's called. So this:

    let _f = || {
        a[0] = 3;
        c.insert(&mut a[..]); // Not work
        // c = Some(&mut a[..]); // Not work
        // c.replace(&mut a[..]); // Not work
        // std::mem::replace(&mut c, Some(&mut a[..])); // Not work
    };

Acts something like this:

struct ClosureF<'a, 'c, 'inner> {
    a: &'a mut [i32; 1],
    c: &'c mut Option<&'inner mut [i32]>,
}

// ...

    let f = ClosureF { a: &mut a, c: &mut c };

...but the body is also taken into consideration, and in order to type check, it is more like...

struct ClosureF<'a, 'c> {
    a: &'a mut [i32; 1],
    c: &'c mut Option<&'a mut [i32]>,
}

The closure also implements one or more of the Fn* traits. As that page says, which it implements is based on how the captures are used, and as this thread shows, that analysis sometimes chooses incorrectly.

All closures implement FnOnce, which would look something like this:

// impl FnOnce<()> for ClosureF<'_, '_> {
impl ClosureF<'_, '_> {
    // type Output = ();
    fn call_once(self) {
        self.a[0] = 3;
        self.c.insert(self.a);
    }
}

So far so good. But what if the compiler tries to implement FnMut?

// impl FnMut<()> for ClosureF<'_, '_>
impl ClosureF<'_, '_> {
    fn call_mut(&mut self) {
        self.a[0] = 3;
        self.c.insert(self.a);
    }
}

It doesn't work:

error: lifetime may not live long enough
 --> src/main.rs:9:23
  |
7 |     fn call_mut(&mut self) {
  |                 ---------
  |                 |
  |                 let's call the lifetime of this reference `'1`
  |                 has type `&mut ClosureF<'2, '_>`
8 |         self.a[0] = 3;
9 |         self.c.insert(self.a);
  |                       ^^^^^^ cast requires that `'1` must outlive `'2`

And the reason is that the lifetime on &mut self is arbitrarily short, and you can only reborrow the &'a mut [i32; 1] for that short lifetime.[1] Because it can be arbitrarily short, it's not long enough to assign to the Option. The borrow must be allowed to be arbitrarily short, because that's how the trait is defined.


Next let me point out more explicitly that even if you could change the trait, it wouldn't directly help. Let's "fix" the above error by making the lifetime on &mut self be long enough:

impl<'a> ClosureF<'a, '_> {
    fn call_mut(&'a mut self) {

Now the method compiles, and you can even call it... but if you try to call it more than once, you'll find that you can't. The problem is that when you make the first call, you exclusively borrow the closure forever, and thus can never call it again. So this "fix" hasn't actually gained you anything over call_once.

Therefore this is a dead-end even if the trait signature wasn't a blocker.


Briefly, some other possible work-arounds are...

  • If you need a FnMut and can change the signature

    • Take a as an argument to the closure instead

    • But the compiler's poor closure inference strikes again, in a different way

    • And you can't call it with &mut a twice for hopefully understandable reasons (but you could call it with a borrow of something else)

  • If you need a FnMut and can't change the signature

  • If you actually need the body to execute multiple times and are actually working with trivially copiable types like i32 and thread safety is not required

  • If you actually need the body to execute multiple times with less trivial values or requirements

    • It's only possible if you can avoid &mut to the same data existing across all calls[2]

    • And requires some gymnastics to avoid reborrowing through the outer &mut self[3]

    • But still may be possible, depending on your use case


  1. Sometimes it's possible to instead replace the inner &'a mut _ and move the original out from &mut self, but there's no reasonable way to do that in this case... at least, as far as the reduced examples have shown. ↩︎

  2. this is why you couldn't call the argument version with &mut a or execute the body more than once in the faux-FnMut version ↩︎

  3. the replacing of the inner &'a mut _ which I mentioned in a previous footnote ↩︎

2 Likes

Thanks for your elaborated and excellent explanation, and I have spent many days to read one by one, also give me a deeper understanding. All the methods listed is enough to solve the problem.
During the process, unfortunately, these error hints successfully leave me in another puzzle.

The question is why it requires cast requires that `'1` must outlive `'2` . Later, after trying and trying, I find it is the famous self reference problem, which is equivalent to the simpler code below:

struct Struct<'x>{
    x: i32,
    y: &'x i32
}
impl<'x> Struct<'x> {
    fn test<'s>(&'s mut self) {
        self.y = &self.x;
    }
}

It outputs the same error:

error: lifetime may not live long enough
   --> src\main.rs:121:13
    |
119 |     impl<'x> Struct<'x> {
    |          -- lifetime `'x` defined here
120 |         fn test<'s>(&'s mut self) {
    |                 -- lifetime `'s` defined here
121 |             self.y = &self.x;
    |             ^^^^^^^^^^^^^^^^ assignment requires that `'s` must outlive `'x`
    |
    = help: consider adding the following bound: `'s: 'x`

I do not know how the compiler deduces 's should outlives 'x according to which rules and correlates 's with 'x. In gut feeling, it is okay to ensure the lifetime of self.x outlives self.y in the same self variable, and just concerns the right side lifetime of self.x not right self. For self.x and self.y have the same lifetime in struct, it satisfies the condition. Really want to know the principle and inference steps of the compiler.

What happens if your variable is moved? Your y will point to invalidated memory. While your mutable reference to self you pass to test is valid, the variable can't be moved. This allows you to create a temporary self-referential struct during 's, not beyond it. You express that with 's: 'x or "'s must outlive 'x." If 'x were to live longer than 's you could move your variable after 's is over but before 'x is finished, causing a dangling y. The lesson to learn here is don't write self-referential types in Rust.

2 Likes

I believe you're making a common mistake of thinking of the value liveness of the fields (and struct) as Rust "lifetimes" -- those '_ things. They are not the same thing. x does not have a '_ lifetime. Rust lifetimes are known at compile time and discarded when compiling; the liveness scope of values is a dynamic property.

There's no way to borrow self.x without borrowing self, since self owns x. There's no way to assign something to self.y with a lifetime less than 'x, as that would make the type invalid at 'x. When you combine these two (assigning &self.x to self.y), you have to borrow self for the lifetime of the reference in the y field -- which corresponds to the lifetime parameter on Struct<'_> -- which results in borrowing Struct<'x> for 'x. And that's the "borrowed forever scenario".

Not being able to assign a shorter &'s i32 to a &'x i32 type or to borrow self.x for 'x through a shorter &'s self are just fundamental parts of how borrowing work. Allowing either one would allow the reference to dangle, for example.[1]


I'll try to walk through the borrow checker rules at play when creating a self-referencial struct like this step by step.

struct Struct<'x>{
    x: i32,
    y: &'x i32
}
  • I want to modify the Struct<'x> (field y):
    • Need a &'s mut Struct<'x>[2]
  • In the modifying function, I need to take a &'x _ to one of the fields (x)
    • Need a &'x Struct<'x> or &'x mut Struct<'x>[3]
  • The "borrow field x while modifying y" is fine due to borrow splitting
  • (But note that &'x mut Struct<'x> is invariant in 'x,[4] which is the "borrow forever" situation)

And this all works out at the function level from a borrow checker perspective... which is why it compiles and you can call it. But then you're left in a situation where the Struct<'x> is borrowed forever,[5] which means you can't take a &mut to it or move it for example.

Like @jofas said, not being able to move Struct<'x> once it becomes self-referencial is necessary to prevent UB: If you could move it after that, the reference would immediately dangle. Rust has no move constructors or the like. More deeply though, the borrow checker isn't really working on that level:

  • Struct<'x> fundamentally cannot be used outside of some region 'x
  • Creating a &'x mut Struct<'x> borrows the struct for the entirety of 'x
  • And that alone seals the deal -- you've already made Struct<'x> unmovable "forever"
    • If you delete the function body that makes Struct<'x> self-referencial without changing the signature, it won't change the error at all

It's actually sort of amazing that the borrow checker lets you get as far as creating the self-referencial struct, while still protecting you from UB, without any self-referencial specific logic.


  1. Consider a Struct<'static> in a local variable: Make a &'static self.x through some shorter borrow, pass it to another thread, return from function, UB. ↩︎

  2. or shared mutability and a &'s Struct<'x> ↩︎

  3. 's: 'x; but the inner lifetime already implies 'x: 's; together this means 's = 'x ↩︎

  4. as is &'x Struct<'x> if 'x (field y) is behind shared mutability ↩︎

  5. usually exclusively, but with shared mutability, perhaps just "shared" borrowed forever ↩︎

2 Likes

From @jofas paste, learned the variable can not be moved. From @quinedot, I got the principle behind the lifetime of self-reference, and by the way read something about the "borrow splitting" and "borrowed forever", which helps me to promote understanding too. Up to now I dare say the responses solve the problem put at the tittle. Thanks for all your kind explanations.

1 Like