Message Passing
Sharing Senders
In std::sync::mpsc, both Sender<T> and SyncSender<T> can be shared across producer threads in two ways:
- By cloning (
tx.clone()): Required when spawning'staticthreads withstd::thread::spawnso each producer owns its own transmitter handle and the channel stays open until the last producer finishes. - By shared reference (
&tx): Enabled becauseSender<T>: SyncandSyncSender<T>: Sync(wheneverT: Send), allowing scoped threads insidestd::thread::scopeto send messages through a single borrowed&Sender<T>without cloning!
Comparing Cloned Sender<T> Handles vs. Borrowed &Sender<T>
Method 1: Cloned Senders (`tx.clone()` for `thread::spawn` `'static` Threads)
Worker 1 owns `Sender<T>` clone ---\
Worker 2 owns `Sender<T>` clone ----+--> Channel stays open until ALL clones drop!
Method 2: Borrowed Sender (`&tx` inside `thread::scope`)
Worker 1 borrows `&Sender<T>` ---\
Worker 2 borrows `&Sender<T>` ----+--> Zero counter increments; `tx` dropped after scope!
| Sharing Approach | Thread Type | Reference-Count Overhead | Automatic Disconnection When Workers Exit? |
|---|---|---|---|
tx.clone() moved into each worker |
thread::spawn or thread::scope |
Atomic increment on clone, decrement on drop | Yes, provided the parent thread drops its original tx! |
&tx borrowed by each worker |
thread::scope |
Zero (&Sender<T> is a plain pointer copy) |
Parent drops tx after thread::scope finishes (or reads rx after scope) |
Embedding a Sender<T> Inside a Cloneable Service Handle
In actor and background-service architectures, a background thread owns the Receiver<Command> while callers hold a lightweight #[derive(Clone)] client struct wrapping Sender<Command>:
use std::sync::mpsc::{self, Sender};
use std::thread::{self, JoinHandle};
enum AuditCommand {
Record { user: String, action: String },
FlushAndExit,
}
#[derive(Clone)]
pub struct AuditClient {
tx: Sender<AuditCommand>,
}
impl AuditClient {
pub fn record(&self, user: &str, action: &str) {
let _ = self.tx.send(AuditCommand::Record {
user: user.to_string(),
action: action.to_string(),
});
}
pub fn shutdown(&self) {
let _ = self.tx.send(AuditCommand::FlushAndExit);
}
}
fn spawn_audit_actor() -> (AuditClient, JoinHandle<Vec<String>>) {
let (tx, rx) = mpsc::channel::<AuditCommand>();
let handle = thread::spawn(move || {
let mut logs = Vec::new();
for cmd in rx {
match cmd {
AuditCommand::Record { user, action } => {
logs.push(format!("{user}:{action}"));
}
AuditCommand::FlushAndExit => break,
}
}
logs
});
(AuditClient { tx }, handle)
}
fn main() {
let (client, actor_handle) = spawn_audit_actor();
thread::scope(|s| {
for (user, action) in [("alice", "login"), ("bob", "upload")] {
// Clone the `AuditClient` handle (which clones the inner `Sender`):
let c = client.clone();
s.spawn(move || {
c.record(user, action);
});
}
});
client.shutdown();
let mut recorded = actor_handle.join().unwrap();
recorded.sort();
println!("actor collected logs: {:?}", recorded);
}
actor collected logs: ["alice:login", "bob:upload"]