Trait object + send


#1

Hi folks,

One thing I don’t quite understand is why Send is required when sharing trait object between threads. For example,

use std::sync::{Arc, Mutex};
use std::thread;

trait Foo {
    fn inc(&mut self, i32) -> i32;
}

struct FooImpl {
    c: i32,
}

impl Foo for FooImpl {
    fn inc(&mut self, c: i32) -> i32 {
        self.c += c;
        self.c
    }
}

fn main() {
    let counter: Arc<Mutex<Box<Foo>>> = Arc::new(Mutex::new(Box::new(FooImpl { c: 0 })));
    let mut threads = Vec::new();

    for _ in 0..10 {
        let counter = counter.clone();
        threads.push(thread::spawn(move || {
            let mut w = counter.lock().unwrap();
            let c = (*w).inc(1);
            println!("counter={}", c);
        }));
    }
    for thread in threads {
        thread.join().unwrap();
    }
}

In the above code, each thread holds a mutex protected copy of the trait object. At any given time, at most one of them will access the object. Because trait object is implemented as fat pointers, and all the threads share the same address space, why can’t Rust just send the fat pointer among spawned threads? In other words, why don’t Rust make all the trait objects implement Send by default?


#2

The underlying object may not be Send, and it would be unsound to send it to another thread. For example, imagine you implement Foo for Rc and then create a trait object pointing at an Rc - you can’t send it to another thread. Or imagine you have a value that’s tied to some thread local - can’t send it either.

If you want to guarantee that this is safe, you can say trait Foo: Send { ... }, which (effectively) means you can only impl this trait for Send types - then your code works just fine.