A channel is a message-passing structure between threads.
A Channel is like a pipe or a river. You put something in one end (the transmitter) and that something travels until it comes out the other end (the receiver).
It is a very safe abstraction because it breaks temporal dependency: the sender does not need to know when (or if) the receiver will be ready, and vice versa. Additionally, it fits perfectly with Rust’s Ownership model.
The std::sync::mpsc Library
In Rust’s standard library, channels are found in the mpsc module.
This acronym stands for: Multiple Producer, Single Consumer.
This means a channel in Rust can have many sending ends (multiple transmitters), but only one receiving end (a single receiver). Imagine many tributaries flowing into a single main river.
Creating a Channel
To create a channel we use mpsc::channel. It returns a tuple with two elements:
tx(Transmitter): The sending end.rx(Receiver): The receiving end.
use std::sync::mpsc;
use std::thread;
fn main() {
// Create the channel
let (tx, rx) = mpsc::channel();
// Spawn a thread and move the transmitter (tx) into it with 'move'
thread::spawn(move || {
let message = String::from("Hello from the thread!");
tx.send(message).unwrap(); // Send the message
});
// In the main thread, wait to receive something
let received = rx.recv().unwrap(); // Blocks until data arrives
println!("Received: {}", received);
}send and recv
tx.send(value): Sends a value through the pipe. Returns aResult. If the receiver (rx) has been destroyed (the receiving thread closed),sendwill return an error to notify us that no one is listening.rx.recv(): Blocks the current thread’s execution until a value arrives. If the transmitter (tx) closes the channel without sending anything, it returns an error.rx.try_recv(): Does not block. It checks if something is available right now. If not, it returns an error immediately and continues execution. Useful for doing other things while waiting.
Channels and Ownership
Rust’s ownership model is clearly visible with channels. send takes the value by value: if it does not implement Copy, ownership is transferred; if it does, a copy is sent.
The sending thread (tx) loses the data. The receiving thread (rx) becomes the new owner.
For moved values, this prevents the sender and receiver from accessing the same data simultaneously. Channels do not eliminate all logical concurrency errors, but they reduce the need for shared mutable memory.
thread::spawn(move || {
let val = String::from("Important data");
tx.send(val).unwrap();
// ❌ Error: 'val' has been moved into the channel. We can no longer use it here.
// println!("Trying to use val: {}", val);
});The compiler guarantees that once sent, you forget about it.
Multiple Producers
As the MPSC name indicates, we can have multiple senders. To do this, we need to clone the transmitter (tx). The receiver (rx) cannot be cloned.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
// Clone the transmitter to give it to another thread
let tx1 = tx.clone();
// Thread 1: Sends messages using the clone
thread::spawn(move || {
let msgs = vec!["Hello", "from", "thread", "1"];
for msg in msgs {
tx1.send(msg).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
// Thread 2: Sends messages using the original
thread::spawn(move || {
let msgs = vec!["Messages", "from", "thread", "2"];
for msg in msgs {
tx.send(msg).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
// Consumer (Main thread):
// We can iterate over rx as if it were an infinite iterator.
// The loop ends when ALL tx (tx and tx1) have been closed (dropped).
for received in rx {
println!("Received: {}", received);
}
}The output will show messages from both threads interleaved, depending on how the operating system schedules the threads, but all will arrive safely at the receiver.
Note on closure:
The iterator for received in rx only ends when all transmitters have been dropped.
If the main function retains an unused tx, the receiver may block indefinitely because the channel remains open and another message could still arrive.