06open 25 min

Concurrency and context: goroutines, channels, and cancellation

Learn Go's concurrency model for Rust developers used to threads or async runtimes.

by the end of this lesson you can

  • Chooses sequential or concurrent execution intentionally
  • Uses goroutines only when they help
  • Accounts for cancellation or request lifetime with context

Overview

Rust developers may arrive from OS threads, channels, Tokio, or other async runtimes. Go treats concurrency as a more ordinary part of application structure, with goroutines, channels, and context used to coordinate work and cancellation.

In Rust, you often

use threads, channels, or async runtimes with explicit ownership and trait bounds governing what can move or be shared.

In Go, the common pattern is

to launch goroutines freely, coordinate with channels when needed, and propagate cancellation and deadlines through context.

why this difference matters

This lesson helps Rust developers stop mapping everything to threads or futures and start reading Go concurrency on its own terms.

Rust

std::thread::spawn(move || {
    worker(jobs);
});

Go

go worker(ctx, jobs)

Deeper comparison

Rust version

let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
    tx.send(load()).unwrap();
});
let result = rx.recv().unwrap();

Go version

resultCh := make(chan Result)
go func() {
    resultCh <- load()
}()
result := <-resultCh

Reflect

When does Go concurrency feel simpler than Rust's models, and what safety guarantees are you giving up in exchange?

what a strong answer notices

A strong answer mentions lightweight startup, easy cancellation flow with context, and fewer compiler-enforced guarantees around shared state.

Rewrite

Translate this Rust concurrent shape into Go using goroutines, channels, or context where appropriate.

Rewrite this Rust

async fn load_dashboard(id: UserId) -> Result<Dashboard, Error> {
    let profile = load_profile(id).await?;
    let billing = load_billing(id).await?;
    Ok(build_dashboard(profile, billing))
}

what good looks like

  • Chooses sequential or concurrent execution intentionally
  • Uses goroutines only when they help
  • Accounts for cancellation or request lifetime with context

Practice

Design a Go background worker with cancellation support for a Rust developer used to thread joins or async tasks.

success criteria

  • Uses context for cancellation
  • Explains where channels help and where they are unnecessary
  • Keeps concurrency structure simple and readable

Common mistakes

  • Expecting goroutines to map neatly onto Rust threads or futures.
  • Ignoring context because Rust cancellation patterns looked different.
  • Assuming Go concurrency safety is enforced as strongly as Rust's.

takeaways

  • This lesson helps Rust developers stop mapping everything to threads or futures and start reading Go concurrency on its own terms.
  • A strong answer mentions lightweight startup, easy cancellation flow with context, and fewer compiler-enforced guarantees around shared state.
  • Uses context for cancellation