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