0

This code compiles:

use std::thread;

struct Foo;

fn use_foo(foo: &Foo) {}

fn main() {
    let foo = Foo {};
    thread::spawn(move || {
        use_foo(&foo);
    });
}

but this code does not:

use std::thread;

struct Foo;

struct Bar<'a> {
    foo: &'a Foo,
}

fn use_bar(bar: Bar) {}

fn main() {
    let foo = Foo {};
    let bar = Bar { foo: &foo };
    thread::spawn(move || {
        use_bar(bar);
    });
}

Instead failing with

error[E0597]: `foo` does not live long enough
  --> src/main.rs:15:26
   |
15 |       let bar = Bar { foo: &foo };
   |                            ^^^^ borrowed value does not live long enough
16 | /     thread::spawn(move || {
17 | |         use_bar(bar);
18 | |     });
   | |______- argument requires that `foo` is borrowed for `'static`
19 |   }
   |   - `foo` dropped here while still borrowed

This bears some similarity to this issue, however here there is only a single reference to foo. Is there any way to get around this indirection introduced by bar and move foo into the closure?

Playground

frankplow
  • 502
  • 1
  • 12
  • 1
    *"Is there any way to ... move `foo` into the closure?"* - yes, by using the first way. Perhaps I'm not understanding the question. The `move` keyword will move variables used in the closure into it, but it won't move things that aren't named. (i.e. it won't move `foo` if you don't use `foo` in the closure) – kmdreko Aug 29 '22 at 17:56
  • 1
    If you want to move `foo` into the closure you have to name it explicitly. That said, if you use the new and improved scoped threads, you may get away without moving `foo`: [payground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bfa46b3370f3b3aa973890e994d8d9c9). – rodrigo Aug 29 '22 at 17:57
  • @kmdreko Sorry, should have added more detail to the question, I realise the example is a little silly. In practice foo_ref would be a more complex structure containing a reference to foo, and perhaps other data. The equivalent to use_foo would then be a function requiring this. The same error results. I will edit the question to make this all a bit clearer. – frankplow Aug 29 '22 at 18:01

1 Answers1

1

There are a few things that prevent the second example from working:

  • The compiler will not move variables into the closure if they aren't used by the closure. The fact that foo is used through bar is not a factor.
  • You cannot move a value that you were only given a shared reference to. In the general sense, other references may exist and would be very put off by even mutating the value.
  • Even if you were to move the value foo, any references derived from it would be invalidated. The compiler prevents this from happening. This means even if you did use both foo and bar in the closure it still wouldn't work. You'd need to recreate bar with a fresh reference to the moved foo.

So in summary, no, you cannot in general take a structure that contains references and convert it or wrap it up into an owned version so that you can send it to another thread.

You can perhaps avoid references by using indexes (if appropriate for your real use-case) or perhaps you could use shared ownership (by using Arc instead of references). Or of course simply delay creating your referencing structure until after it has been moved to the thread.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • I am wrong in thinking that this is something which could be added to the compiler at a later date though? Points 2 and 3 both apply to the first case as well, just the compiler is able to detect them statically. – frankplow Aug 29 '22 at 18:44
  • 1
    I don't think a move-by-reference mechanism will be added to the language any time soon, and I doubt it would even be possible for all but the most trivial cases. – kmdreko Aug 29 '22 at 18:56
  • 1
    You can look into the various self-referential crates to see if they work for you: ouroboros, self-cell, escher. They are designed to help bundle owners and referees into a single structure. – kmdreko Aug 29 '22 at 19:13