C.W.K.
Stream
Lesson 03 of 05 · published

Channels — Message Passing

~11 min · concurrency, channels, mpsc

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

One way threads coordinate is by passing messages instead of sharing memory. Rust's standard channel — mpsc — lets one thread send values to another, with ownership transferring along the way.

mpsc: multiple producer, single consumer

mpsc::channel() returns a (Sender, Receiver) pair. Threads holding a Sender call .send(value); the thread holding the Receiver calls .recv() (blocking) or iterates it. 'mpsc' = many senders, one receiver: you can clone the Sender to feed one receiver from several threads.

Sending transfers ownership

When you send a value, ownership moves into the channel and then to the receiver — the sender can't use it afterward. This is the same move semantics again, and it's why channels are race-free: a value is only ever owned by one side at a time. No shared mutable state means no lock and no data race.

'Do not communicate by sharing memory; share memory by communicating.' Channels move ownership between threads, so only one thread holds a value at any moment. It's often simpler to reason about than locks — no shared state to protect, just values flowing one direction.

Channels vs shared state

Two valid styles: message passing (channels, this lesson) and shared state (Arc<Mutex>, next lesson). Channels suit pipelines and producer/consumer setups — data flows one way. Shared state suits many threads reading and updating one structure. Real programs mix both; knowing which fits a problem is the skill.

Code

Multiple producers feeding one receiver·rust
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    // Spawn producers; each send transfers ownership into the channel
    for id in 0..3 {
        let tx = tx.clone(); // clone the Sender per thread (multiple producer)
        thread::spawn(move || {
            tx.send(format!("hello from {id}")).unwrap();
        });
    }
    drop(tx); // drop the original so the channel closes once producers finish

    // The receiver iterates until every sender is gone
    for msg in rx {
        println!("{msg}");
    }
}

External links

Exercise

Build a producer/consumer: spawn 4 worker threads that each send their squared id down a channel, and have main collect all results into a Vec and sum them. Make sure the receiver loop terminates — what do you have to do with the senders for that to happen?
Hint
Clone a sender into each worker and drop the original tx in main after spawning. The for msg in rx loop ends when the last sender (the last worker's clone) is dropped.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.