# Rust Ownership — Derived Application > Applies Atelier's domain principles to Rust's ownership model specifically. Rust's distinctive strength (Send/Sync, lifetimes, borrowing) earns a dedicated ownership doc rather than an `rs-types.md`. > Derives from `domains/` docs; introduces no new P-rules (D-063). > See `languages/rust.md` for the language first-principles stub. ## Ownership and Move Semantics (Concurrency P1 Immutability by Default, C1 Correctness) - **Ownership is unique:** at any time, exactly one owner holds a value. Assignment passes ownership (`let y = x;` — `x` is moved, not copied). The compiler rejects use-after-move. - **`Copy` types (integers, `bool`, `&T`) duplicate on assignment; everything else moves.** A `struct` is `Copy` only if all fields are; opt in via `#[derive(Copy, Clone)]` only for small, cheap-to-copy types. - **Pass by `&T` for read-only, `&mut T` for mutation:** a borrow does not transfer ownership; the caller retains the value after the callee returns. - **Applies `concurrency/P1` (immutability by default):** `&T` is shared and immutable; `&mut T` is exclusive and mutable. The compiler enforces "one or many, never both" — aliasing XOR mutation, statically. ```rust let s = String::from("hello"); let t = s; // s moved into t // println!("{}", s); // error: use of moved value let n = 5; let m = n; // i32 is Copy: n still usable println!("{} {}", n, m); ``` ## Borrowing and Lifetimes (C1 Correctness, Data P7 Type Fidelity, Concurrency P3 Boundaries are Locks) - **`&'a T` ties a borrow to a lifetime `'a`:** the borrow cannot outlive the owner. Lifetimes are static — the compiler rejects dangling references. - **Lifetime elision when unambiguous:** `fn first<'a>(s: &'a str) -> &'a str` is elided to `fn first(s: &str) -> &str` (one input → output lifetime). When ambiguous, name the lifetime. - **`'static` is the longest lifetime (the whole program):** not "until I drop it." Use `'static` only for values that genuinely live forever (string literals, `const`s); leaking to `'static` to satisfy the checker is a bug. - **`Ref<'a, T>` and `RefMut<'a, T>` from `RefCell` are runtime-checked borrows:** the borrow rules still apply, checked at runtime instead of compile time. A second `RefMut` panics. - **Applies `concurrency/P3` (boundaries are locks):** `&mut T` is the compile-time lock — exclusive access is the boundary; no runtime mutex needed for single-threaded aliasing discipline. ```rust fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() > b.len() { a } else { b } // borrow tied to both inputs } fn dangling() -> &str { // compile error: missing lifetime let s = String::from("local"); &s // error: s drops at end of fn } ``` ## Send and Sync (Concurrency P1 Immutability by Default, Concurrency P3 Boundaries are Locks, C1 Correctness) - **`Send`:** a type `T: Send` may be moved across thread boundaries. Most types are `Send`; `Rc` is not (shared non-atomically refcounted). - **`Sync`:** a type `T: Sync` may be shared (`&T`) across threads. `RefCell` is `!Sync` (interior mutability without atomics); `Mutex` is `Sync` (it synchronizes). - **The compiler enforces `Send`/`Sync` at the thread-spawn boundary:** `std::thread::spawn(move || { ... })` requires the closure's captures to be `Send`. - **Applies `concurrency/P1` and `concurrency/P3`:** `Send` is the move-across-boundary contract; `Sync` is the share-across-boundary contract. Data races are a compile error, not a runtime detector. This is Rust's distinctive strength over Go's race detector. ```rust use std::rc::Rc; use std::sync::Arc; let rc = Rc::new(5); // std::thread::spawn(move || { println!("{}", rc) }); // error: Rc is !Send let arc = Arc::new(5); std::thread::spawn(move || { println!("{}", arc) }); // ok: Arc is Send+Sync ``` ## Shared Mutation: Arc, Mutex, RwLock (Concurrency P3 Boundaries are Locks, Concurrency P5 Lock Minimization) - **`Arc` for shared ownership across threads:** atomic refcounted. Clone increases the count; the last drop frees `T`. - **`Mutex` for exclusive mutation across threads:** `lock()` blocks until exclusive; the guard `MutexGuard` derefs to `&mut T` and releases on drop. - **`RwLock` for read-heavy, `Mutex` for write-heavy:** RwLock allows multiple readers or one writer. For most cases, `Mutex` is simpler and faster; prefer it unless reads dominate by 10x+. - **Hold the lock for the smallest scope:** `let g = m.lock().unwrap();` then drop `g` before I/O. RAII releases on scope exit; explicit `drop(g)` clarifies intent. - **Applies `concurrency/P5` (lock minimization):** prefer message passing (`mpsc` channels) over locks. When a lock is needed, scope it minimally. ```rust use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let c = Arc::clone(&counter); handles.push(thread::spawn(move || { let mut g = c.lock().unwrap(); *g += 1; // g drops here, lock released })); } for h in handles { h.join().unwrap(); } println!("{}", *counter.lock().unwrap()); ``` ## Interior Mutability (Concurrency P1 Immutability by Default, C1 Correctness) - **`Cell` for `Copy` types, `RefCell` for non-`Copy`:** interior mutability moves the borrow check from compile time to runtime. `RefCell::borrow_mut()` panics on a second mutable borrow. - **`Mutex`/`RwLock` for thread-safe interior mutability:** the runtime check is the lock, not a panic. Use these across threads; `RefCell` only single-threaded. - **`UnsafeCell` is the primitive; never use directly:** `Cell`, `RefCell`, `Mutex` are safe wrappers. Direct `UnsafeCell` is `unsafe` and opts out of the aliasing guarantee. - **Applies `concurrency/P1`:** interior mutability is the exception, not the default. Reach for it when an API must present `&self` while mutating internally (e.g., a cache); document why. ```rust use std::cell::RefCell; struct Cache { inner: RefCell>, } impl Cache { fn get(&self, id: &str) -> Option { // &self (immutable) but mutates internally self.inner.borrow_mut().entry(id.to_string()).or_insert_with(|| fetch()).clone() } } ``` ## Drop and RAII (C1 Correctness, Concurrency P3 Boundaries are Locks) - **`Drop` runs when the owner goes out of scope:** no `defer`, no `finally`. A `MutexGuard` releases, a `File` closes, a `JoinHandle`... does not join (a dropped `JoinHandle` detaches). - **`Drop` is deterministic:** it runs at scope exit, not GC time. This is why `Arc`'s refcount is precise and `Mutex` release is timely. - **`ManuallyDrop` to opt out:** for FFI types whose destructor you must call manually. Rare in application code; common in `unsafe` bindings. - **`Drop` order: fields in declaration order, then the struct itself.** A field that another field's `Drop` depends on must be declared last. ```rust struct Resource { name: String } impl Drop for Resource { fn drop(&mut self) { println!("dropping {}", self.name); // runs at scope end } } fn main() { let _r = Resource { name: "x".into() }; // _r drops here, prints "dropping x" } ``` ## Cross-References - `domains/concurrency/first-principles.md` — Concurrency P1 Immutability, P3 Boundaries are Locks, P5 Lock Minimization. - `domains/data/first-principles.md` — Data P7 Type Fidelity (lifetimes are the type-level fidelity for references). - `domains/concurrency/patterns.md` — message-passing vs lock patterns applied via `Arc`/`Mutex`/`mpsc`. - `domains/errors/patterns.md` — `?` propagation relies on ownership transfer of the error. - `languages/rs-async.md` — async borrows (`Pin`/`&mut`) build on the lifetime model here. - `languages/rs-testing.md` — `Send`/`Sync` tests and ownership-based property tests.