Theme
Q038. When should an API take ownership, borrow a value, or use shared ownership?
Short Answer
Take ownership when the callee must store, consume, transform, or transfer a value independently of the caller. Borrow with &T for temporary observation and &mut T for temporary in-place mutation. Use Rc or Arc only when multiple owners genuinely need independent lifetimes. Good signatures expose retention and cost instead of hiding clones or reference counting.
Deep Dive
Prefer the least powerful ownership contract that satisfies the implementation:
- accept
&strrather thanStringwhen only reading text during the call; - accept
Stringorimpl Into<String>when the callee must retain owned text; - accept
&mut Twhen mutation should remain visible through the caller's value; - return an owned
Twhen the result must outlive borrowed inputs; - use
Cow<'a, T>when an API usually borrows but occasionally needs an owned modification; - use
Rc<T>for single-threaded shared ownership andArc<T>for cross-thread shared ownership.
Shared ownership introduces runtime counting and possible cycles, so it should model a real lifecycle requirement rather than bypass ownership design. APIs should also avoid cloning internally merely to simplify lifetime signatures without documenting the cost.
Internal Model
Borrowed APIs carry compiler-checked lifetime relationships and usually no ownership bookkeeping. Owned parameters transfer drop responsibility to the callee. Rc and Arc clone operations update runtime counters, and cleanup occurs when the final strong owner is dropped.
The signature lets callers predict allocation, retention, mutation, and synchronization boundaries before reading the implementation.
Example
rust
struct Registry {
names: Vec<String>,
}
impl Registry {
fn add(&mut self, name: impl Into<String>) {
self.names.push(name.into());
}
fn contains(&self, name: &str) -> bool {
self.names.iter().any(|stored| stored == name)
}
}
fn main() {
let mut registry = Registry { names: Vec::new() };
registry.add("worker");
println!("{}", registry.contains("worker"));
}Common Mistakes
- Taking ownership when a short-lived borrow fully satisfies the operation.
- Adding
Arc<Mutex<T>>by default instead of identifying actual sharing and mutation requirements. - Hiding expensive clones inside APIs whose signatures appear to borrow cheaply.
Follow-ups
- When is
Cow<'a, T>preferable to always cloning? - How should an API expose ownership transfer of OS handles?
- What cycle risks do
Rc<T>andArc<T>introduce?
Related
- Q005. What does it mean for a value to own a resource?
- Q020. What is the difference between
CopyandClone? - Q037. How does ownership cross function parameters, return values, and method receivers?
References
- The Rust Programming Language
- Rust API Guidelines
- Rust Standard Library