0Pricing
Learn Rust Coding · Lesson

Crossbeam Channels

Advanced channels.

Crossbeam Channels is a free Learn Rust Coding lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Beyond std mpsc

The standard mpsc channel is single consumer: only one receiver. The crossbeam-channel crate offers multi-producer, multi-consumer (mpmc) channels with more features and often better performance.

Highlights:

  • Cloneable Receivers, not just senders.
  • A powerful select! macro to wait on many channels.
  • Bounded, unbounded, and special channels like ticks.

Adding the Dependency

Crossbeam is an external crate, so add it to Cargo.toml:

[dependencies]
crossbeam-channel = "0.5"

Then import its functions. Because this needs Cargo and an external crate, the snippets here illustrate the API rather than running standalone.

// Cargo.toml
// [dependencies]
// crossbeam-channel = "0.5"

use crossbeam_channel::unbounded;

fn main() {
    let (s, r) = unbounded();
    s.send("hello").unwrap();
    println!("{}", r.recv().unwrap());
}

unbounded and bounded

Crossbeam offers two main constructors:

  • unbounded() grows as needed; send never blocks.
  • bounded(cap) has a fixed buffer; send blocks when full, providing backpressure.

A bounded(0) channel is a rendezvous channel where send and recv hand off directly.

use crossbeam_channel::bounded;
use std::thread;

fn main() {
    let (s, r) = bounded(2);
    thread::spawn(move || {
        for i in 1..=3 {
            s.send(i).unwrap();
        }
    });
    while let Ok(v) = r.recv() {
        println!("got {}", v);
    }
}

Multiple Consumers

The key advantage: you can clone the Receiver. Several worker threads can pull from the same channel, and each message goes to exactly one of them. This is the foundation of a work-stealing thread pool.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (s, r) = unbounded();
    let mut workers = vec![];
    for id in 0..3 {
        let rx = r.clone();
        workers.push(thread::spawn(move || {
            while let Ok(job) = rx.recv() {
                println!("worker {} got job {}", id, job);
            }
        }));
    }
    for job in 0..6 { s.send(job).unwrap(); }
    drop(s);
    for w in workers { w.join().unwrap(); }
}

The select! Macro

select! lets one thread wait on multiple channel operations and act on whichever is ready first. It is like a match over channel events, ideal for combining inputs from several sources.

use crossbeam_channel::{unbounded, select};
use std::thread;

fn main() {
    let (s1, r1) = unbounded();
    let (s2, r2) = unbounded();
    thread::spawn(move || { s1.send("from one").unwrap(); });
    thread::spawn(move || { s2.send("from two").unwrap(); });
    for _ in 0..2 {
        select! {
            recv(r1) -> msg => println!("r1: {}", msg.unwrap()),
            recv(r2) -> msg => println!("r2: {}", msg.unwrap()),
        }
    }
}

Timeouts with select!

select! supports a default arm and a recv(after(duration)) timer arm, so you can give up waiting after a timeout instead of blocking forever. This is essential for responsive systems.

use crossbeam_channel::{unbounded, select, after};
use std::time::Duration;

fn main() {
    let (_s, r) = unbounded::<i32>();
    select! {
        recv(r) -> msg => println!("received {:?}", msg),
        recv(after(Duration::from_millis(100))) -> _ => {
            println!("timed out waiting for a message");
        }
    }
}

try_send and try_recv

Like the standard library, crossbeam provides non-blocking variants. try_send fails immediately if a bounded channel is full; try_recv fails immediately if no message is ready. Both return a descriptive error you can match on.

use crossbeam_channel::{bounded, TrySendError};

fn main() {
    let (s, _r) = bounded(1);
    s.send(1).unwrap();
    match s.try_send(2) {
        Ok(()) => println!("sent"),
        Err(TrySendError::Full(v)) => println!("full, kept {}", v),
        Err(TrySendError::Disconnected(v)) => println!("closed, kept {}", v),
    }
}

tick and Periodic Work

The tick(duration) function returns a receiver that delivers a message at a fixed interval. Combine it with select! to run periodic tasks alongside other channels, like a heartbeat or polling loop.

use crossbeam_channel::{tick, select};
use std::time::Duration;

fn main() {
    let ticker = tick(Duration::from_millis(50));
    let mut count = 0;
    while count < 3 {
        select! {
            recv(ticker) -> _ => {
                count += 1;
                println!("tick {}", count);
            }
        }
    }
}

A Pipeline of Stages

Crossbeam excels at building pipelines: stage one produces, stage two transforms, stage three consumes. Each stage runs on its own thread connected by channels, and cloned receivers let you scale any stage to multiple workers.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (in_s, in_r) = unbounded();
    let (out_s, out_r) = unbounded();
    thread::spawn(move || {
        for n in in_r { out_s.send(n * n).unwrap(); }
    });
    for n in 1..=4 { in_s.send(n).unwrap(); }
    drop(in_s);
    for sq in out_r { println!("square: {}", sq); }
}

When Crossbeam Beats std

Reach for crossbeam-channel when you need:

  • Multiple consumers sharing one queue (worker pools).
  • select! over many channels with timeouts.
  • Periodic timers via tick integrated into selection.

For a simple one-producer-one-consumer pipe, the standard mpsc is fine and needs no dependency.

Channels Are Closed by Dropping

Just like std, crossbeam channels close when all senders (for the receiver side) or all receivers (for the sender side) are dropped. Iterating a receiver ends after the last sender drops. Always drop or scope your senders so worker loops can exit cleanly.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (s, r) = unbounded();
    let h = thread::spawn(move || {
        let total: i32 = r.iter().sum();
        println!("total: {}", total);
    });
    for i in 1..=5 { s.send(i).unwrap(); }
    drop(s); // closes channel so the loop ends
    h.join().unwrap();
}

Quick Check

Test your understanding of crossbeam channels.

Recap

You explored crossbeam channels:

  • They are mpmc: both senders and receivers can be cloned.
  • unbounded() and bounded(n) control buffering and backpressure.
  • select! waits on multiple channels, with timeouts via after and periodic tick.
  • try_send/try_recv are non-blocking.
  • Use crossbeam for worker pools and complex pipelines; std mpsc for simple pipes.

Frequently asked questions

Is the “Crossbeam Channels” lesson free?

Yes — the full text of “Crossbeam Channels” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.

What will I learn in “Crossbeam Channels”?

Advanced channels. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Learn Rust Coding?

No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Crossbeam Channels” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Learn Rust Coding lesson?

Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. mpsc Channels
  2. Sharing State with Arc/Mutex
  3. Scoped Threads
  4. Crossbeam Channels
← Back to Learn Rust Coding