Learn / Rust Practical Systems
Rust Memory and Concurrency
A consolidated foundations lesson preserving 2 focused PTesting lessons without duplicating an unrelated authored PTLearn topic.
Course: Rust Practical Systems. Level: Intermediate. Topic: Systems reliability.
Stage: advanced - Architecture - Language and runtime foundations. Connect rust memory and concurrency to the professional workflow for Rust Systems.
Outcomes
- Explain stack allocation, heap allocation, deterministic drop, and RAII
- Use Box, Rc, Arc, RefCell, and Mutex for appropriate ownership patterns
- Recognize interior mutability and reference-counting trade-offs
- Spawn threads and transfer ownership into closures
- Communicate with channels and synchronize with Arc and Mutex
- Explain Send and Sync as compile-time concurrency contracts
Concepts
- Memory Model and Smart Pointers
- Deterministic cleanup
- Smart pointer selection
- Rust for Reliable Systems and Services foundations
- Guided practice
- Concurrency
- Ownership across threads
- Channels and shared state
Concept flow
Show how language and runtime foundations moves from trigger to implementation outcome in Rust Systems.
- Language model
- Runtime behavior
- Engineering decision
- Verification evidence
Session flow
- Model Memory Model and Smart Pointers (concept, 60 min) — Name the decisions behind Memory Model and Smart Pointers before writing code.
- Explain stack allocation, heap allocation, deterministic drop, and RAII
- Explain where Memory Model and Smart Pointers belongs in job orchestration service.
- Build the vertical slice (walkthrough, 108 min) — Implement the smallest useful slice in legacy/rust-systems/rust-memory-and-concurrency.txt.
- Use Box, Rc, Arc, RefCell, and Mutex for appropriate ownership patterns
- Connect Deterministic cleanup to a working example.
- Verify and harden (exercise, 72 min) — Reference solution: recursive: Box
single-thread shared: Rc
thread shared config: Arc
thread shared counter: Arc<Mutex<T>>
- Recognize interior mutability and reference-counting trade-offs
- Record one risk or follow-up before moving on.
- Memory Layout and Smart Pointers: Deterministic cleanup (concept, 60 min) — Rust drops values when their owners leave scope. This pattern, often called RAII, makes resource cleanup predictable for files, sockets, locks, and heap allocations.
- Retained source example: Drop at scope end
struct Trace(&'static str);
impl Drop for Trace {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
fn main() {
let _trace = Trace("request");
println!("working");
}
Expected output: working
drop request
Drop runs automatically at the end of the scope, even when the value is not manually freed.
- Memory Layout and Smart Pointers: Smart pointer selection (walkthrough, 60 min) — Box<T> owns heap data with one owner. Rc<T> enables multiple owners on one thread. Arc<T> enables atomic reference counting across threads. RefCell<T> moves borrow checking to runtime for single-threaded interior mutability; Mutex<T> protects shared mutable state across threads.
- Retained source example: Shared read-only configuration
use std::sync::Arc;
#[derive(Debug)]
struct Config {
service_name: String,
}
fn clone_handle(config: &Arc<Config>) -> Arc<Config> {
Arc::clone(config)
}
Arc::clone increments a reference count; it does not deep-copy the Config.
- Memory Layout and Smart Pointers: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Start with single ownership and borrowing before introducing reference counting.
- Practice: Use Arc::clone or Rc::clone to make shared ownership explicit.
- Practice: Keep lock scopes short and avoid complex work while holding a Mutex guard.
- Avoid: Using Rc in code that must cross threads.
- Avoid: Using RefCell to avoid understanding ownership instead of to express real interior mutability.
- Avoid: Assuming Arc<T> makes the inner T mutable or logically thread-safe by itself.
- Memory Layout and Smart Pointers: references (review, 2 min) — Original references retained from the legacy library.
- Rust book: smart pointers: https://doc.rust-lang.org/book/ch15-00-smart-pointers.html
- Rustonomicon: https://doc.rust-lang.org/nomicon/
- Threads, Channels, and Shared State: Ownership across threads (concept, 60 min) — A thread closure often uses move to take ownership of data it needs. The compiler checks that moved values are safe to send to another thread through the Send trait.
- Retained source example: Move data into a thread
use std::thread;
fn main() {
let message = String::from("work item");
let handle = thread::spawn(move || {
println!("processing {message}");
});
handle.join().expect("worker thread panicked");
}
Expected output: processing work item
move transfers message into the spawned closure so it outlives the parent stack frame.
- Threads, Channels, and Shared State: Channels and shared state (walkthrough, 60 min) — Channels transfer messages and ownership between threads. Shared state uses Arc for multiple thread owners and Mutex or RwLock to coordinate mutation. Choose message passing when ownership transfer is natural; choose locks when shared mutable state is truly the model.
- Retained source example: Shared counter
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..4 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut guard = counter.lock().expect("lock poisoned");
*guard += 1;
}));
}
for handle in handles {
handle.join().expect("thread panicked");
}
}
Arc shares ownership across threads, while Mutex ensures one mutable access at a time.
- Threads, Channels, and Shared State: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Prefer ownership transfer by channel when it matches the workflow.
- Practice: Keep critical sections short and release locks before calling unknown code.
- Practice: Join spawned threads or use scoped concurrency so failures are observed.
- Avoid: Assuming Arc removes the need for synchronization around mutation.
- Avoid: Holding a Mutex guard across expensive work.
- Avoid: Ignoring join results and losing panics from worker threads.
- Threads, Channels, and Shared State: references (review, 2 min) — Original references retained from the legacy library.
- Rust book: fearless concurrency: https://doc.rust-lang.org/book/ch16-00-concurrency.html
- std::thread: https://doc.rust-lang.org/std/thread/
Code example
rust in legacy/rust-systems/rust-memory-and-concurrency.txt.
fn main() {}
Walkthrough examples
- Rust Memory and Concurrency in a job orchestration service — A team is extending an Axum service with a companion CLI and needs this lesson's pattern to be clear enough for review, testing, and future maintenance.
- File: legacy/rust-systems/rust-memory-and-concurrency.txt
- File: tests/rust-memory-and-concurrency.spec
- File: docs/rust-systems/rust-memory-and-concurrency.md
- Start from the provided rust snippet and make the intent visible in names and boundaries.
- Apply the checklist item "Explain the language or runtime rule in your own words" before adding extra behavior.
- Write down how the implementation changes when Deterministic cleanup fails or becomes slow.
- Drop at scope end — Drop runs automatically at the end of the scope, even when the value is not manually freed.
- Retained source code:
struct Trace(&'static str);
impl Drop for Trace {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
fn main() {
let _trace = Trace("request");
println!("working");
}
- Expected output: working
drop request
- Compare the example with the canonical PTLearn implementation.
- Shared read-only configuration — Arc::clone increments a reference count; it does not deep-copy the Config.
- Retained source code:
use std::sync::Arc;
#[derive(Debug)]
struct Config {
service_name: String,
}
fn clone_handle(config: &Arc<Config>) -> Arc<Config> {
Arc::clone(config)
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Interior mutability for test doubles — RefCell allows mutation through &self, but borrow rule violations panic at runtime.
- Retained source code:
use std::cell::RefCell;
struct Recorder {
events: RefCell<Vec<String>>,
}
impl Recorder {
fn record(&self, event: String) {
self.events.borrow_mut().push(event);
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Move data into a thread — move transfers message into the spawned closure so it outlives the parent stack frame.
- Retained source code:
use std::thread;
fn main() {
let message = String::from("work item");
let handle = thread::spawn(move || {
println!("processing {message}");
});
handle.join().expect("worker thread panicked");
}
- Expected output: processing work item
- Compare the example with the canonical PTLearn implementation.
- Shared counter — Arc shares ownership across threads, while Mutex ensures one mutable access at a time.
- Retained source code:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..4 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut guard = counter.lock().expect("lock poisoned");
*guard += 1;
}));
}
for handle in handles {
handle.join().expect("thread panicked");
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Channel ownership transfer — Sending a String moves ownership through the channel to the receiver.
- Retained source code:
use std::sync::mpsc;
fn send_job() {
let (tx, rx) = mpsc::channel();
tx.send(String::from("reindex")).expect("receiver alive");
let job = rx.recv().expect("message available");
assert_eq!(job, "reindex");
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
Practice
- Choose a smart pointer: Choose Box, Rc, Arc, RefCell, or Mutex for: heap-owning recursive type, shared single-thread graph, shared config across threads, mutable shared counter across threads. The guided runner checks pointer names.
- Starter code: recursive:
single-thread shared:
thread shared config:
thread shared counter:
- Hint: Recursive enums often need Box.
- Hint: Arc is thread-safe reference counting.
- Hint: Mutex provides synchronized mutation.
- Reference solution: recursive: Box
single-thread shared: Rc
thread shared config: Arc
thread shared counter: Arc<Mutex<T>>
- Accepted answers: Box | Rc | Arc | Mutex
- Choose message passing or shared state: For a worker pool that receives independent jobs, choose channels or Arc<Mutex<T>> as the primary design. Explain the ownership reason. The guided runner checks deterministic keywords.
- Starter code: primary design:
reason:
- Hint: Independent jobs can be owned by one worker at a time.
- Hint: A channel transfers ownership cleanly.
- Reference solution: primary design: channels
reason: each job is moved to exactly one worker, avoiding shared mutable state.
- Accepted answers: channels | ownership
Checklist
- Explain the language or runtime rule in your own words
- Run one focused example and record its output
- Name one failure mode or tradeoff
- Keep the verification evidence with the lesson
Quiz prompts
- A teammate wants to hide Memory Model and Smart Pointers inside a convenient helper. What should you check first? — Place Memory Model and Smart Pointers at the boundary that keeps job orchestration service behavior explicit, testable, and reviewable.
- Which artifact best proves this Rust Systems lesson is ready for review? — Production-ready learning needs evidence: a test, trace, command, screenshot, or log that catches the risk again.
- What is a key difference between Rc<T> and Arc<T>? — Rc is single-threaded; Arc pays atomic overhead to support thread-safe reference counting.
- What do Send and Sync describe? — Send means ownership can move to another thread; Sync means shared references can be used from multiple threads.
Flashcards
- In Rust Systems, what should you remember about Memory Model and Smart Pointers? Memory Model and Smart Pointers matters here because it supports "Explain stack allocation, heap allocation, deterministic drop, and RAII".
- In Rust Systems, what should you remember about Deterministic cleanup? Deterministic cleanup matters here because it supports "Use Box, Rc, Arc, RefCell, and Mutex for appropriate ownership patterns".
- In Rust Systems, what should you remember about Smart pointer selection? Smart pointer selection matters here because it supports "Recognize interior mutability and reference-counting trade-offs".
- In Rust Systems, what should you remember about Rust for Reliable Systems and Services foundations? Rust for Reliable Systems and Services foundations matters here because it supports "Spawn threads and transfer ownership into closures".
Labs
- Ship a rust memory and concurrency slice — Extend an Axum service with a companion CLI with a small but reviewable feature that proves the lesson's architecture in code.
- Choose a smart pointer: Choose Box, Rc, Arc, RefCell, or Mutex for: heap-owning recursive type, shared single-thread graph, shared config across threads, mutable shared counter across threads. The guided runner checks pointer names.
- Starter code: recursive:
single-thread shared:
thread shared config:
thread shared counter:
- Hint: Recursive enums often need Box.
- Hint: Arc is thread-safe reference counting.
- Hint: Mutex provides synchronized mutation.
- Reference solution: recursive: Box
single-thread shared: Rc
thread shared config: Arc
thread shared counter: Arc<Mutex<T>>
- The implementation demonstrates Memory Model and Smart Pointers without hidden global state.
- At least one test or verification step covers the main success path.
- The review notes explain the tradeoff behind Deterministic cleanup.
Challenge
- Review-ready rust memory and concurrency (Core) — Turn the lesson work into a pull-request-sized change for job orchestration service. Include the code, verification notes, one explicit tradeoff, and an updated concept diagram that names the riskiest handoff.
- All checklist items are either implemented or documented with a reason.
- The change can be understood from legacy/rust-systems/rust-memory-and-concurrency.txt plus one short note.
- The concept diagram names ownership, failure handling, and verification points.
- A teammate could run the verification steps without asking for hidden context.
Canonical lesson URL