One object has a reference to the other, but moving both of them into one closure is forbidden

Code:

fn exec<F: FnMut()->() + 'static>(mut f: F) {
    f();
}
#[derive(Debug)]
struct Stru<'a> {
    x: &'a i32,
}
fn main() {
    let x = 13;
    let stru = Stru {x: &x };
    exec(move||{
        println!("{}",x);
        println!("{:?}",stru);
    });
}

And here are the compiler massages:

error[E0597]: `x` does not live long enough
  --> src/main.rs:10:25
   |
10 |       let stru = Stru {x: &x };
   |                           ^^ borrowed value does not live long enough
11 | /     exec(move||{
12 | |         println!("{}",x);
13 | |         println!("{:?}",stru);
14 | |     });
   | |______- argument requires that `x` is borrowed for `'static`
15 |   }
   |   - `x` dropped here while still borrowed

These problems are very common when I am dealing with winit. In order to use variables inside winit's event_loop.run(), I only have one solution: making stru take the ownership of x.
Is there any other way to deal with it?
In my opinion, when both moved into a closure, the compiler needs to change the reference to the object inside the closure, rather than the object still outside, because it has already been moved and can no longer be used outside.
For the closure passed to event_loop.run() will be executed many times, to create the object inside the closure to make it get the new reference of the moved object is obviously a waste of performance.
These are my shallow understandings. Please point out if there are any mistakes.

Are smart pointers like Box or Rc an option for you?

I'm not quite sure I follow your reasoning here. When you call move the reference is moved into the closure body. The problem is that F has the 'static lifetime-bound in your example. Even with move, Stru<'a> will not become Stru<'static> all of a sudden.

1 Like

Moves are always blind memcpy()s in Rust. There are no move constructors (which is for the better – otherwise, it would be very hard to write a fast Vec implementation, for example). The compiler also can't dynamically know all the references that point to a certain object – what if the object is behind an Rc or even worse, a raw pointer? The compiler therefore can't just "update" pointers when an object moves – what you want is impossible.

1 Like

Note that x isn't moved but copied (I removed the 'static bound to make the example compile):

fn exec<F: FnMut()->()>(mut f: F) {
    f();
}
#[derive(Debug)]
struct Stru<'a> {
    x: &'a i32,
}
fn main() {
    let x = 13;
    let stru = Stru {x: &x };
    exec(move||{
        println!("{}",x);
        println!("{:?}",stru);
    });
    drop(x); // `x` wasn't moved and isn't even dropped here
}

(Playground)

What you could do is this, but not sure if that's what you want/need:

fn exec<F: FnMut()->() + 'static>(mut f: F) {
    f();
}
#[derive(Debug)]
struct Stru<'a> {
    x: &'a str,
}
fn main() {
    let x = "Thirteen".to_string();
    exec(move||{
        let stru = Stru {x: &x };
        println!("{}",x);
        println!("{:?}",stru);
    });
    //drop(x); // now `x` has been moved, i.e. this would fail to compile
}

(Playground)

The reference would have to be updated every time you moved the closure, not just when you created it. If it did what you suggest when creating the closure in your example, the reference would immediately dangle (instant UB) when it was passed to exec. (Think of the closure as a struct holding the things it captured.)

There are further problems in more complicated scenarios.

let obj = ...;
let borrowed: &_ = func(&obj);
// No way of knowing what `borrowed` points to

More generally you'd need a new kind of reference that knows it's relative to something else and ways to track what that something is. If you know these things yourself, you can just make your code do that manually.


Also as other pointed out, it also wouldn't help on its own for this scenario, because you need &'static. If you need references in there, they need to be to either static memory or to things that you have leaked. If leaking is ok, you can even make it self-referential (it may not be as simple as that toy example for your use case). Note that things you leak will never be dropped or otherwise deallocated.

1 Like

Thanks for your advice. I have found the problem in my case, which is a little difference from the scenario above.
Next time, I will try to avoid using the code above, or try using static or leak to solve it.

Thanks for your advice. I have found the problem in my case, which is a little difference from the scenario above.

Thanks for your advice. I have found the problem in my case, which is a little difference from the scenario above.
But I still wounder if rust could provide a way to choose to reborrow a value when they are moved into a closure.

Thanks for your advice. I have found the problem in my case, which is a little difference from the scenario above.
And I think I may need to enhance I understanding of lifetime.

After using Rc, I no longer think there is a need to do this. Because Rc is truly a good tool in this senario!

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.