Sending trait objects between threads in Rust
Asked Answered
L

1

75

I'd like to send a trait object between threads, but can't figure out if it's possible. It seems like it might not be, as they apparently do not fulfill the Send trait.

The following code demonstrates what I'm trying to do:

use std::{
    sync::mpsc::{channel, Receiver, Sender},
    thread,
};

trait Bar {
    fn bar(&self);
}

struct Foo {
    foo: i32,
}

impl Bar for Foo {
    fn bar(&self) {
        println!("foo: {}", self.foo);
    }
}

fn main() {
    let foo = Box::new(Foo { foo: 1 }) as Box<dyn Bar>;

    let (tx, rx): (Sender<Box<dyn Bar>>, Receiver<Box<dyn Bar>>) = channel();

    thread::spawn(move || {
        tx.send(foo).unwrap();
    });

    let sent = rx.recv().unwrap();

    sent.bar();
}

This fails with the following message:

error[E0277]: `dyn Bar` cannot be sent between threads safely
   --> src/main.rs:25:5
    |
25  |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `dyn Bar` cannot be sent between threads safely
    |
    = help: the trait `std::marker::Send` is not implemented for `dyn Bar`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::ptr::Unique<dyn Bar>`
    = note: required because it appears within the type `std::boxed::Box<dyn Bar>`
    = note: required because it appears within the type `[closure@src/main.rs:25:19: 27:6 tx:std::sync::mpsc::Sender<std::boxed::Box<dyn Bar>>, foo:std::boxed::Box<dyn Bar>]`

Trying to send a plain, unboxed trait object results in a bunch of other errors, mostly complaining about not fulfilling Send + Sized.

I'm still fairly new to Rust, so I'm not sure if there's something I'm missing, but I get the impression that there isn't a way to convince the compiler to make the trait object Send.

If it isn't currently possible, is there any work currently under way that may allow for this in the future?

Lonlona answered 3/9, 2014 at 16:32 Comment(0)
M
80

It's possible. You can add a Send constraint to a trait object like this:

let foo = Box::new(Foo { foo: 1 }) as Box<dyn Bar + Send>;

let (tx, rx): (Sender<Box<dyn Bar + Send>>, Receiver<Box<dyn Bar + Send>>) = channel();
Misapprehend answered 3/9, 2014 at 16:37 Comment(4)
Oh, huh, that's simple enough. I guess I didn't quite understand how trait objects were actually handled. So it's not the trait that has a kind, it's the underlying object, right? So you just have to let the compiler know that the box is holding something with the trait, which also happens to fulfill Send. I think I get it now.Lonlona
This might be useful to someone: In my case, I also required Sync trait, as in Box<dyn Bar + Send + Sync>. (My use case was for a web server using warp library).Ommatophore
I found the simplest thing was to require Send on all implementations of my trait like this: trait Bar : Send { .... Obviously you may not want to do that if you might have some uses that don't require Send.Busch
In "The Rust Programming Language", chapter 16, directly implementing Send is considered "unsafe". When would you consider implementing Send on an object "safe"?Nuno

© 2022 - 2024 — McMap. All rights reserved.