Rust Tokio Broadcast
A broadcast channel is a channel that sends every message to every listener. A watch channel holds one shared value for many listeners.
Every listener gets a copy
One send, and each subscriber receives its own copy of the message. With mpsc, only one of them could have taken it:
let (tx, _first) = broadcast::channel::<String>(8);
let mut listener_a = tx.subscribe();
let mut listener_b = tx.subscribe();
tx.send(String::from("server restarting")).unwrap();
println!("A heard: {}", listener_a.recv().await.unwrap());
println!("B heard: {}", listener_b.recv().await.unwrap());Fall too far behind, and you skip
broadcast is a ring buffer. A receiver that falls too far behind gets a Lagged error and skips the messages it missed:
let (tx, _rx) = broadcast::channel::<i32>(2); // ring buffer holds 2
let mut slow = tx.subscribe();
tx.send(1).unwrap();
tx.send(2).unwrap();
tx.send(3).unwrap(); // message 1 is overwritten
match slow.recv().await {
Err(e) => println!("fell behind: {e}"),
Ok(n) => println!("got {n}"),
}watch keeps only the newest value
A watch channel holds one value. Send again, and the old value is simply replaced โ no queue builds up:
let (tx, rx) = watch::channel(20); // current temperature
tx.send(25).unwrap();
tx.send(31).unwrap(); // 25 is overwritten, unread
println!("latest = {}", *rx.borrow());Read now with borrow, wait with changed
borrow() reads the current value at once. changed().await waits until a new value replaces it:
let (tx, mut rx) = watch::channel(0);
println!("start = {}", *rx.borrow()); // borrow() reads now, marks seen
tx.send(5).unwrap();
rx.changed().await.unwrap(); // a change since the borrow
println!("after change = {}", *rx.borrow());Example
// Cargo.toml: tokio = { version = "1", features = ["full"] }
use tokio::sync::broadcast;
#[tokio::main]
async fn main() {
let (tx, _first) = broadcast::channel::<String>(8);
let mut listener_a = tx.subscribe();
let mut listener_b = tx.subscribe();
tx.send("server restarting in 5 minutes".to_string()).unwrap();
println!("A heard: {}", listener_a.recv().await.unwrap());
println!("B heard: {}", listener_b.recv().await.unwrap());
}Key points
- Use broadcast when each listener must see all messages, like chat or live events
- Use watch for shared state that changes: a config value, a shutdown flag, a progress number
- Rule of thumb: every message matters โ broadcast.
- Only the latest value matters โ watch.
- Each message goes to one worker โ mpsc.
In this example, one broadcast channel gets two subscribers. A single send puts the message into the channel once. Both listeners then receive their own copy of it โ with mpsc, only one of them could have taken it. This is exactly the shape of a chat room or an announcement feed.
Note
broadcast receivers are made with sender.subscribe() โ a receiver only sees messages sent after it subscribed.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Rust Playground
Exercise
Your app shows the current temperature. Updates arrive ten times a second, and only the newest matters. Which channel fits?