06open 25 min

Concurrency and shared state: from goroutines to threads, channels, and ownership

Learn how Rust handles concurrent work and shared state for developers coming from goroutines and channels.

by the end of this lesson you can

  • Chooses threads or channels intentionally
  • Accounts for who owns the shared data
  • Uses synchronization tools only where shared mutation is real

Overview

Go makes concurrency feel lightweight and available everywhere. Rust also supports threads and channels, but it asks much stricter questions about ownership, mutation, and whether data can be shared safely across threads.

In Go, you often

launch goroutines easily and coordinate with channels, mutexes, or context once the design needs it.

In Rust, the common pattern is

to use threads, channels, and synchronization types with ownership and trait bounds enforcing what may be sent or shared safely.

why this difference matters

This lesson matters because many Go developers expect concurrency to feel immediately familiar. The primitives overlap, but Rust makes the safety model much more explicit.

Go

go worker(jobs)

Rust

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

Deeper comparison

Go version

cache := map[string]int{}
var mu sync.Mutex

go func() {
    mu.Lock()
    cache["a"]++
    mu.Unlock()
}()

Rust version

let cache = std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashMap::new()));
let shared = std::sync::Arc::clone(&cache);

std::thread::spawn(move || {
    let mut cache = shared.lock().unwrap();
    *cache.entry("a".to_string()).or_insert(0) += 1;
});

Reflect

Why can Rust concurrency feel heavier at first but easier to trust once shared-state rules are explicit?

what a strong answer notices

A strong answer mentions ownership across threads, safe mutation boundaries, and fewer accidental races surviving into runtime.

Rewrite

Translate this Go worker-pool sketch into a Rust design that makes ownership and shared state explicit.

Rewrite this Go

jobs := make(chan Job)
go runWorker(jobs)

what good looks like

  • Chooses threads or channels intentionally
  • Accounts for who owns the shared data
  • Uses synchronization tools only where shared mutation is real

Practice

Design a Rust in-memory cache with shared state for multiple workers reading and updating entries.

success criteria

  • Explains where Arc and Mutex are needed
  • Keeps ownership of shared state explicit
  • Avoids assuming goroutine-style ergonomics map directly

Common mistakes

  • Expecting Rust concurrency to feel as casual as launching a goroutine.
  • Fighting Arc<Mutex<_>> without first asking whether sharing is necessary.
  • Treating thread-safety constraints as incidental instead of core design rules.

takeaways

  • This lesson matters because many Go developers expect concurrency to feel immediately familiar. The primitives overlap, but Rust makes the safety model much more explicit.
  • A strong answer mentions ownership across threads, safe mutation boundaries, and fewer accidental races surviving into runtime.
  • Explains where Arc and Mutex are needed