Ownership Basics
10 examples to get you started with ownership - 7 basic and 3 intermediate.
Prerequisites
- Rust Basics - variables and functions
- Ownership Preview - first look at moves
Basic Examples
1. Single Owner Rule
fn main() {
let s = String::from("owner");
consume(s);
// println!("{s}"); // error: moved
}
fn consume(s: String) {
println!("{s}");
}Stringowns heap bytes- Passing
stoconsumemoves ownership - After a move, the original binding cannot be used
consumedropssat end of scope
Related: Move Semantics & Copy -
Copytypes
2. Scope and Drop
fn main() {
{
let s = String::from("temp");
println!("{s}");
} // drop runs here
println!("done");
}- Owners go out of scope at end of block
Drop::dropfrees resources (heap forString)- No GC - deterministic cleanup
- Order: last created, first dropped (stack unwind)
Related: Smart Pointers Basics - custom
Drop
3. Move Between Variables
fn main() {
let a = vec![1, 2, 3];
let b = a;
println!("{:?}", b);
}Vecis notCopy- Assignment moves, not bitwise duplicate
- Only one binding owns the allocation
- Cheap move: copies pointer/length/capacity on stack
4. Function Return Transfers Ownership
fn make_greeting() -> String {
String::from("hello")
}
fn main() {
let g = make_greeting();
println!("{g}");
}- Caller becomes owner of returned value
- Return is a move (or NRVO-elided copy of stack value)
- Ownership can flow in and out of functions
- Return
Stringwhen caller should own data
5. Partial Move from Structs
struct Pair { a: String, b: String }
fn main() {
let p = Pair { a: "x".into(), b: "y".into() };
let a = p.a; // partial move
// println!("{}", p.b); // OK
println!("{a}");
}- Moving one field does not move the whole struct if other fields remain
- Entire struct is moved if all fields moved or struct is
Copy - Design structs to minimize partial-move confusion
- Prefer borrowing fields when possible
6. Borrowing Avoids Move
fn len(s: &String) -> usize { s.len() }
fn main() {
let s = String::from("rust");
println!("{}", len(&s));
println!("still own: {s}");
}&Stringborrows without taking ownership- Owner remains valid after borrow ends
- Many read-only APIs should take references
- See Borrowing section for
&mut
Related: Borrowing & References
7. Ownership with Vec Growth
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2);
let last = v.pop().unwrap();
println!("{last}, len={}", v.len());
}Vecowns a heap bufferpushmay reallocate and move elementspopremoves ownership of last element to caller- Iterators can borrow elements without moving the
Vec
Intermediate Examples
8. Custom Drop Side Effects
struct LogOnDrop(&'static str);
impl Drop for LogOnDrop {
fn drop(&mut self) {
println!("dropping {}", self.0);
}
}
fn main() {
let _x = LogOnDrop("resource");
}- Implement
Dropfor cleanup beyond memory dropcalled automatically, not recursively by valuestd::mem::drop(x)runs early drop explicitly- Do not call
Drop::dropmanually on values
9. Rc for Shared Ownership (Preview)
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);
println!("refs={}", Rc::strong_count(&a));
drop(b);
println!("{a}");
}Rccounts owners in single-threaded codeRc::cloneincrements count, not deep clone of string- Value dropped when count hits zero
Arcis the thread-safe variant
Related: Rc & Arc
10. Ownership in Collections
fn main() {
let mut map = std::collections::HashMap::new();
map.insert("key".to_string(), vec![1, 2]);
if let Some(v) = map.remove("key") {
println!("{:?}", v);
}
}HashMapowns keys and valuesinsertmoves value inremovemoves value out (leaves absent key)- Borrow with
getfor read-only lookup
Related: Ownership Best Practices
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.