简体   繁体   中英

How can I return a type containing an Rc from a thread?

I'm spawning a thread which builds what is essentially a tree, which is needed sometime later and can, therefore, be computed in an off-thread. For the sake of example, think of a Foo { inner: HashMap<Bar, Rc<FooBar>> } . The thread's closure does not capture anything from the surrounding environment.

The problem is that the Rc makes the entire type !Send . This precludes Foo from being returned to the spawning thread. Effectively, the Rc "poisons" Foo .

I fail to see the reason why a type that is created by the spawned thread and returned still needs to be Send : It forces all inner types (in this case the Rc in the HashMap , in Foo , to be Send . This is unfixable from the point of view of the spawned thread. It forces the spawned thread to use atomic counting all the time, while there is actually zero possibility for both threads to access the refcounter simultaneously.

Is there a way to return (not share!) a type that contains an Rc from a thread? Am I missing something in the understanding of Send ?

[T]here is actually zero possibility for both threads to access the refcounter simultaneously.

But the compiler can't understand that, so you have to say "no really, trust me, I know it looks dodgy but it's actually safe." That's what unsafe is for.

It should be as simple as

unsafe impl Send for Foo {}

From the Nomicon :

  • Rc isn't Send or Sync (because the refcount is shared and unsynchronized).

It's not safe to make Rc itself Send because Rc exposes an interface that would be unsafe if sent between threads. But Foo , as you describe it, exposes an interface that isn't unsafe to send between threads, so just unsafe impl it and be on your way.

Assuming, that is, that the interface is safe to send between threads. Foo can't have a method that clones and returns an internal Rc , for example (because you could use that to effectively "smuggle" a bare Rc into another thread via Foo ).

If Foo is not always safe to send, but you happen to know that some particular Foo is safe to send, a better approach would be to temporarily wrap it in a type that is Send , as Shepmaster's answer suggests.

Rust the language knows nothing about the fact that when a thread ends it can no longer have any references. In fact, it doesn't know anything about threads "starting" or "ending", either.

From the languages point of view, it's possible that one of the Rc 's clones is still owned by the thread. Once the type is on a thread, that's the end of it.


When you have something that you the programmer knows but the compiler cannot, that's a case for unsafe code.

I am not 100% sure, but, to the best of my knowledge , so long as all interlinked Rc s are on the same thread, it's actually safe. This should be the case for returning a value from a thread.

use std::{rc::Rc, thread};

type MyTreeThing = Rc<i32>;
struct ReturningAnRcAcrossThreadsIsSafe(MyTreeThing);

// I copied this from Stack Overflow without reading the text and
// stating why I think this code is actually safe.
unsafe impl Send for ReturningAnRcAcrossThreadsIsSafe {}

fn main() {
    let t = thread::spawn(|| ReturningAnRcAcrossThreadsIsSafe(Rc::new(42)));

    let a = t.join().unwrap().0;

    println!("{}", a);
}

This answer differs from trentcl's answer in that the unsafe code has a much smaller scope — only the value that is returned across the thread is covered by it. Their answer marks the entire MyTreeThing type as safe to send across threads, regardless of context. This is fine, so long as you take care to know that all related Rc s are always moved wholesale between threads.

See also:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM