I'm trying to find a way to group the types of broadcast messages I send between threads with a trait bound. The channel is a multi producer multi consumer type from crossbeam. I can send a single message down the channel just fine:
use rand::random;
use std::fmt::Debug;
pub trait Broadcastable: Send + Debug {}
#[derive(Debug)]
pub struct MessageTypeOne {
some_item: usize,
}
impl Broadcastable for MessageTypeOne {}
#[derive(Debug)]
pub struct MessageTypeTwo {
some_item: String,
some_other: usize,
}
impl Broadcastable for MessageTypeTwo {}
pub type BroadcastMessage = Box<dyn Broadcastable>;
fn send_any_message(sender: crossbeam_channel::Sender<BroadcastMessage>, message: BroadcastMessage) {
sender.send(message).expect("Something went terribly wrong while sending message.");
}
fn main() {
let (sender, receiver) = crossbeam_channel::unbounded();
let handle = std::thread::spawn(move || {
let receivers: Vec<_> = (0..10)
.map(|_| receiver.clone()).collect();
receivers.iter().enumerate().for_each(|(i, rcv)| {
if let Ok(msg) = rcv.recv() {
println!("Receiver {} received message {:?}", i+1, msg);
}
});
});
if random() {
send_any_message(sender,
Box::new(
MessageTypeOne {
some_item: 1,
}
));
} else {
send_any_message(sender,
Box::new(
MessageTypeTwo {
some_item: "Hey there wassup buddy?".to_string(),
some_other: 2,
}
));
}
handle.join().expect("Could not join handle for some reason.");
}
However this message is only received by one recipient. For other recipients to receive the message I've got to send it in a loop and clone the message struct. But when I do that I get the error that tells me "Broadcastable cannot be made into an object":
How to solve this and have the message sent to multiple receivers? Thank you.
An interface that implements Clone can't be a trait object, as the error message says, because the return value of the clone method is Self and would have a different size for each concrete type. In other words, the concrete return type and therefore the signature of the clone function would be different for each implementation of the trait.
I'm not sure this is the most succinct way to do it, but I was able to fix your playground by adding a method to your trait that clones and boxes the value, and then using this method to implement Clone for your dyn type:
This is because crossbeam_channel is not a broadcast channel. Each message is delivered to an arbitrarily selected receiver — sending 10 messages does not guarantee that 10 different receivers will receive them. The primary use case of this type of channel is to deliver work items to a thread pool, so they seek to deliver messages to whichever thread isn’t busy.
In my experience, in most cases where you want to clone and broadcast a dyn Trait, a better solution is to use Arc<dyn Trait>. This way you get cheap clones (no additional allocations), don’t need add any special trait methods, and it's semantically equivalent unless the concrete type under the dyn Trait has interior mutability, or you need specifically ownership of a Box<dyn Trait>.
What @kpreid said is correct, and my reply was based on the assumption that you want to clone the messages for some reason, e.g., to have separate ownership for each message processed. If that assumption is incorrect, using Arc<dyn Broadcastable> is best as they said.
Now that we're talking about your actual application, I have to question why you are passing a dyn Broadcastable since each receiver will have to either downcast or only convert to a string using the Debug trait. Can you say more about this? Another approach is to send an enum with a variant for each type of message, so that the recipient can process each type of message differently.
I know iteration for broadcast is a bad idea, but crossbeam as of yet doesn't have a dedicated broadcast channel.
This looks like the best solution if indeed the message is read-only. Arc::clone will just clone the pointer to the underlying trait object. Whereas with Box a new allocation must be made every time we get need a clone. This works flawlessly and it's a very compact solution!:
The application in question has a lot of (around 30 different) message types that can be sent between nodes. A central "hub" node handles the broadcasting of messages coming from single senders to multiple recipients. Instead of building send functions for 30 different type of messages, it's much easier to use dynamic dispatch. The other option is to use a huge enum, which is ugly.
I haven't thought much about the recipient side though. As you point out concrete types of messages might turn out to be major challenge. In that case I'll convert the BroadcastMessage to a 30 variant enum type. This'll look ugly but might turn out to be a better solution. Thanks for pointing out the potential trouble ahead.
I checked the broadcast capable channel libs, but very few of them are non-async implementations. And they appear to be not used much? I wonder why that is the case. Anyway, with any open-source lib I add to a project, my rule of thumb is: choose very very carefully.
I did some tinkering with runtime type reflection and how it suits my needs, and it turns out to be a neat solution. It works well in situations like mine where we go from many options to a few options specific to a receiver type.
Disadvantages of this approach are: it's a bit more verbose than the huge enum solution and the vtable lookups would not be ideal in performance-critical applications. Without further ado, here is the updated playground with runtime type reflection if anyone is interested:
I've wondered too. I suspect, but have not tried to verify, that it is a matter of correlation:
Developers who are interested in message passing may be more likely to also prefer async programming.
The types of programs that need broadcast channels are also likely to be written in async.
Note that you can straightforwardly use an async channel in a program that does not otherwise use async, by simply wrapping uses of the channel in pollster::block_on() — an utterly minimal async executor which contains no more code than necessary to execute one Future.
That’s fair. But it does you no good to have only high-quality dependencies if those don’t actually do the thing you need. The only way to reliably broadcast using crossbeam would be to make one channel for each receiving thread — and then you need to manage your own list of senders, and the buffered messages are duplicated.
You might be able to abuse thiserror into generating the enum for you, though I would recommend hand-writing a custom macro_rules! macro instead so you can implement the traits and downcasting methods you actually want.
A macro is indeed the right solution to implement Broadcastable for n different types:
The only problem now is the fake looped broadcasting with crossbeam mpmc. I'll look into some of the libraries that provide this functionality reliably.
After some looking around I found pollster method of using async libs in non-async contexts you mentioned is the best approach here for the time being. I decided to replace crossbeam_channel with tokio::sync::broadcast and pollster's executor handling the futures. Both of these libraries are top-notch and used by millions of people. Tokio broadcast channel also has the bonus advantage that if you use try_recv() method, then the function doesn't return a future and you don't need to await anything or use pollster at all. You can normally unpack the result and have fun with it.
Though pollster doesn't exist in playground, so the updated playground won't run, here's the (hopefully) "final" version of the playground with Arc<dyn Trait> improvement, runtime type reflection on the receiver side and tokio broadcast without spinning a runtime with pollster if anyone is still interested in how this saga ultimately ended (hopefully).