4

I have some mutable state I need to share between threads. I followed the concurrency section of the Rust book, which shares a vector between threads and mutates it.

Instead of a vector, I need to share a generic struct that is ultimately monomorphized. Here is a distilled example of what I'm trying:

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use std::marker::PhantomData;

trait Memory {}

struct SimpleMemory;

impl Memory for SimpleMemory {}

struct SharedData<M: Memory> {
    value: usize,
    phantom: PhantomData<M>,
}

impl<M: Memory> SharedData<M> {
    fn new() -> Self {
        SharedData {
            value: 0,
            phantom: PhantomData,
        }
    }
}

fn main() {
    share(SimpleMemory);
}

fn share<M: Memory>(memory: M) {
    let data = Arc::new(Mutex::new(SharedData::<M>::new()));

    for i in 0..3 {
        let data = data.clone();
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            data.value += i;
        });
    }

    thread::sleep(Duration::from_millis(50));
}

The compiler complains with the following error:

error[E0277]: the trait bound `M: std::marker::Send` is not satisfied
  --> src/main.rs:37:9
   |
37 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^
   |
   = help: consider adding a `where M: std::marker::Send` bound
   = note: required because it appears within the type `std::marker::PhantomData<M>`
   = note: required because it appears within the type `SharedData<M>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<SharedData<M>>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<SharedData<M>>>`
   = note: required because it appears within the type `[closure@src/main.rs:37:23: 40:10 data:std::sync::Arc<std::sync::Mutex<SharedData<M>>>, i:usize]`
   = note: required by `std::thread::spawn`

I'm trying to understand why M would need to implement Send, and what the appropriate way to accomplish this is.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
w.brian
  • 16,296
  • 14
  • 69
  • 118

1 Answers1

9

I'm trying to understand why M would need to implement Send, ...

Because, as stated by the Send documentation:

Types that can be transferred across thread boundaries.

If it's not Send, it is by definition not safe to send to another thread.

Almost all of the information you need is right there in the documentation:

  • thread::spawn requires the callable you give it to be Send.
  • You're using a closure, which is only Send if all the values it captures are Send. This is true in general of most types (they are Send if everything they're made of is Send, and similarly for Sync).
  • You're capturing data, which is an Arc<T>, which is only Send if T is Send.
  • T is a Mutex<U>, which is only Send if U is Send.
  • U is M. Thus, M must be Send.

In addition, note that thread::spawn also requires that the callable be 'static, so you need that too. It needs that because if it didn't require that, it'd have no guarantee that the value will continue to exist for the entire lifetime of the thread (which may or may not outlive the thread that spawned it).

..., and what the appropriate way to accomplish this is.

Same way as any other constraints: M: 'static + Send + Memory.

DK.
  • 55,277
  • 5
  • 189
  • 162
  • 3
    The confusing aspect about the `Send + 'static` constraint is that in the Rust book they're wrapping a `Vec` that at first glance doesn't appear to have a `'static` lifetime since it's defined in the `main` method. Upon further inspection it is `'static` because it's a literal value. This is not immediately apparent to everyone. When I originally added the `Send` constraint it complained that it needed to be `'static`, it seemed to defy what the example was conveying, hence the question. The condescending tone of your answer is unwarranted and reflects poorly on the rust community. – w.brian Oct 14 '16 at 15:51
  • @w.brian It's not that it's a literal value, it's that it can live as long as it wants to. In other words, there isn't anything in the type itself that limits how long it's valid (such as a reference to non-static data): the answer to the question "when does a value of this type become invalid" is "never". – DK. Oct 15 '16 at 03:19
  • 3
    @w.brian As for my "tone", I'm afraid that's in your head: if I wanted to do condescending, I wouldn't have bothered to go through and get links for every specific page of documentation so you could follow my reasoning more easily. Moreso, I find it offensive and petty for you to malign the whole community for the perceived "tone" of one person. Irrespective of myself, this community has some of the most helpful, welcoming and hard-working volunteers I've ever seen. If you want to have a problem with someone, have it with *me.* – DK. Oct 15 '16 at 03:26
  • 3
    I appreciate the answer, but it implies I didn't do my due diligence. And whether you like it or not, you represent the community when you provide answers for this tag. – w.brian Oct 15 '16 at 03:41