Rust: Rust Ownership
Last updated: 2026-08-26
Ownership is Rust's most distinctive feature—it allows Rust to guarantee memory safety without the need for a garbage collector.
Ownership is the most fundamental difference between Rust and other languages. Once you understand it, you understand the essence of Rust's design.
1. What You'll Learn
- Three Principles of Ownership: Each value has exactly one owner
- Values are automatically released (dropped) when they go out of scope
- "move" meaning: transfer of ownership
- The Difference Between the
CopyandCloneTraits - Data Storage Methods for Stacks and Heaps
- Ownership Transfer in Function Calls
2. Conceptual Diagrams
flowchart LR
A["Value Creation<br>let s = String::from(...)"] --> B["Owner Binding<br>s Has this value"]
B --> C["Transfer of Ownership move<br>let s2 = s; s Failure"]
C --> D["Out of scope drop<br>} Automatic Memory Release"]
D --> E["Memory Security<br>No Double Release,Non-swinging pointer"]
3. The Story of a Library
(1) The Agony: The Book Was Borrowed by Two People at the Same Time
Tom manages a small library. He encountered a classic problem:
- Alice borrowed a copy of Programming in Rust and read up to page 50.
- Bob wanted the same book, so Tom lent it to him again.
- As it turned out, Alice wrote a note on page 50, and Bob wrote a note on page 100.
- When the book was finally returned, the annotations were all jumbled together, and it was impossible to tell who had written them.
- To make matters worse, two people edited the same page at the same time, causing the page to become fragmented.
"If only a book could be borrowed by just one person at a time..."
(2) Rust Ownership Rules
fn main() {
let book = String::from("Rust Programming"); // book is the owner of this book
// let book2 = book; // ❌ If you write it this way,book Ownership was transferred to book2
// println!("{}", book); // ❌ book Can no longer be used
// ✅ The Correct Approach:Only one person can hold ownership at a time.
println!("{} Belongs to Tom The Library", book);
} // book Automatically destroyed here——No manual intervention required free
Rust's ownership rules are like a library's "one person at a time" policy: Every value has exactly one owner at any given time. When the owner leaves the scope, the value is automatically destroyed.
4. The Three Principles of Ownership
graph TB
A[The Three Principles of Ownership] --> B[Principles1: Each value has exactly one owner.]
A --> C[Principles2: It is destroyed as soon as it leaves its scope.]
A --> D[Principles3: Ownership can be transferred(move)]
B --> E[let s = String::from("hi")]
C --> F[} Automatically call at the end drop]
D --> G[let s2 = s; // s No longer valid]
(1) Detailed Explanation of the Principles
| Principle | Description | Analogy |
|---|---|---|
| Single Owner | At any given time, only one person "owns" each value | A library book can only be checked out by one person at a time |
| End of Scope = Destroy | Automatically called when a variable goes out of scope drop |
Books are automatically returned when they are due |
| Transfer of Ownership (move) | Ownership is transferred during assignment or parameter passing; the original variable becomes invalid | A book is passed from one person to another |
5. Stacks and Heaps
To understand ownership, you must first understand the difference between the stack and the heap:
| Dimension | Stack | Heap |
|---|---|---|
| Allocation Speed | Very fast (push/pop) | Slower (requires finding free memory) |
| Data Storage | Data of a known size at compile time | Data of unknown or dynamically changing size |
| Typical Types | i32,bool,f64,Array |
String,Vec,Box |
| Memory Management | Automatic (function call stack frames) | Requires manual management or ownership-based management |
(3) Copy vs Clone vs Move Comparison
| Operation | Syntax | Is the original variable valid? | Performance overhead | Applicable types |
|---|---|---|---|---|
| Copy | let b = a; |
Valid | Very low (bitwise copy of the stack) | i32, f64, bool, char, tuples (including Copy types) |
| Clone | let b = a.clone(); |
Valid | High (stack memory allocation) | String, Vec<T>, Box<T> |
| Move | let b = a; |
Expires | 0 (only copies the pointer) | String, Vec<T>, Box<T> |
If a type implements
Copy, assignment results in an automatic copy; if it does not implementCopy, assignment results in an automatic move. Use.clone()for an explicit deep copy when you need to preserve the original variable.
fn main() {
// Data on the Stack:Fixed size,Copy Semantics
let x: i32 = 5; // Allocation on the Stack 4 Byte
let y = x; // Make a copy, x and y are both valid.
println!("x={}, y={}", x, y); // ✅ All are acceptable
// Load the data:Varies in size,move Semantics
let s1 = String::from("hello"); // s1 On the stack(ptr/len/cap),The actual data is on the heap.
let s2 = s1; // ❌ Ownership from s1 Transfer to s2
// println!("{}", s1); // ❌ Compilation Error:s1 Has been moved
println!("{}", s2); // ✅ s2 It's the new owner.
}
6. Examples of Ownership
▶ Example 1: "move" semantics—transfer of ownership (Difficulty ⭐⭐)
Output:
s1: Rust
s2: Rust
a: 42, b: 42
// ============================================
// Demonstration of Ownership Transfer:Occurs during assignment move
// ============================================
fn main() {
let s1 = String::from("Rust");
let s2 = s1; // s1 Ownership was transferred to s2
// println!("s1: {}", s1); // ❌ Compilation Error!s1 Has been moved
println!("s2: {}", s2); // ✅ s2 I am the owner now
// Integer types are Copy, So it won't move
let a = 42;
let b = a; // a The value is copied to b
println!("a: {}, b: {}", a, b); // ✅ Both are effective.
}
Output:
s2: Rust
a: 42, b: 42
Output:
Acquire ownership: Newly created string
Scalar types such as integers implement the
Copytrait, so assignments involve copying rather than moving.Stringdoes not implementCopy, so assignments involve moving. After a move, the original variable becomes invalid; this is a key design feature in Rust to prevent "double free."
▶ Example 2: Passing Ownership in Function Calls (Difficulty ⭐⭐)
Output:
Acquire ownership: Newly created string
hello
s2: <s2>
Passing back and forth
s3_back: <s3_back>
Inside a function: Newly created string
// ============================================
// Transfer of Ownership in Parameter Passing and Return Values
// ============================================
fn take_ownership(s: String) {
println!("Acquire ownership: {}", s);
} // s Destroyed here(drop)
fn give_ownership() -> String {
let s = String::from("Newly created string");
s // Ownership is returned to the caller
}
fn main() {
let s1 = String::from("hello");
take_ownership(s1); // s1 Ownership was transferred to the function
// println!("{}", s1); // ❌ s1 Expired
let s2 = give_ownership(); // Acquiring Ownership from a Function
println!("s2: {}", s2); // ✅ s2 Ownership
// Send it in and then send it back
let s3 = String::from("Passing back and forth");
let s3_back = takes_and_returns(s3);
// println!("{}", s3); // ❌ s3 Has been moved
println!("s3_back: {}", s3_back); // ✅
}
fn takes_and_returns(s: String) -> String {
println!("Inside a function: {}", s);
s // Return ownership to the caller
}
Output:
Acquire ownership: hello
s2: Newly created string
Inside a function: Passing back and forth
s3_back: Passing back and forth
When a function is called, ownership of the passed-in parameters is transferred to the function. When the function returns a value, ownership is transferred back to the caller. This is what is meant by "ownership flowing between functions"—it's the same principle as "whoever holds the key can open the door."
▶ Example 3: Comparing "Copy" and "Clone" (Difficulty: ⭐⭐⭐)
Output:
Copy: num1=100, num2=100
Clone: s1=Cloning required, s2=<s2>
[1, 2, 3]
v2: [1, 2, 3]
// ============================================
// Copy(Auto-Copy) vs Clone(Explicit Cloning)
// ============================================
fn main() {
// --- Copy Type:Automatic Copy on Assignment ---
let num1 = 100;
let num2 = num1; // Auto-Copy,num1 Still valid
println!("Copy: num1={}, num2={}", num1, num2); // ✅
// --- Clone Type:Must be explicitly called .clone() ---
let s1 = String::from("Cloning required");
let s2 = s1.clone(); // Explicit Cloning,s1 Still valid
println!("Clone: s1={}, s2={}", s1, s2); // ✅
// --- Neither Clone Nor Copy: only move ---
let v1 = vec![1, 2, 3];
let v2 = v1; // move!v1 Failure
// println!("{:?}", v1); // ❌
println!("v2: {:?}", v2); // ✅
}
Output:
Before the upgrade: <format_user(&alice)>
After the upgrade: <format_user(&alice)>
Upgrade Again: <format_user(&alice)>
Bob After the bonus points are added: <format_user(&bob)>
All users: <names>
Alice Final Score: <alice.score>
Bob Final Score: <bob.score>
Copy: Automatically performs a bit-by-bit copy during assignment; the original variable remains valid (applies to data on the stack). Clone: Requires an explicit call to the
.clone()method; applies to data on the heap. Cloning is more expensive (it requires allocating heap memory), so Rust is designed such that "you must explicitly choose to clone."
▶ Example 4: Patterns for Acquiring and Returning Ownership in Functions (Difficulty ⭐⭐⭐)
Output:
Before the upgrade: <format_user(&alice)>
After the upgrade: <format_user(&alice)>
Upgrade Again: <format_user(&alice)>
Bob After the bonus points are added: <format_user(&bob)>
All users: <names>
Alice Final Score: <alice.score>
Bob Final Score: <bob.score>
// ============================================
// Common Patterns:Send it in and then send it back(Get+Return)
// ============================================
struct User {
name: String,
score: i32,
}
fn boost_score(mut user: User, bonus: i32) -> User {
user.score += bonus;
user
}
fn format_user(user: &User) -> String {
format!("{}: {} pts", user.name, user.score)
}
fn main() {
let alice = User {
name: String::from("Alice"),
score: 80,
};
println!("Before the upgrade: {}", format_user(&alice));
let alice = boost_score(alice, 15);
println!("After the upgrade: {}", format_user(&alice));
let alice = boost_score(alice, 10);
println!("Upgrade Again: {}", format_user(&alice));
let mut bob = User {
name: String::from("Bob"),
score: 60,
};
let bonus = 20;
bob.score += bonus;
println!("Bob After the bonus points are added: {}", format_user(&bob));
let names = vec![alice.name.clone(), bob.name.clone()];
println!("All users: {:?}", names);
println!("Alice Final Score: {}", alice.score);
println!("Bob Final Score: {}", bob.score);
}
Output:
Before the upgrade: Alice: 80 pts
After the upgrade: Alice: 95 pts
Upgrade Again: Alice: 105 pts
Bob After the bonus points are added: Bob: 80 pts
All users: ["Alice", "Bob"]
Alice Final Score: 105
Bob Final Score: 80
Ownership of structures is transferred in the same way as for basic types.
boost_scoreOwnership is acquired through a parameter and returned after modification—this is the "acquire-and-return" pattern. Using a reference (&User) allows read-only access without acquiring ownership.
▶ Example 5: Interaction Between Ownership and Sets (Difficulty ⭐⭐⭐)
Output:
Original vector: <words>
The First Word: <first>
Remove Index 1: <removed>, Remaining: <words>
Pop up: <val>
The vector is empty: [10, 20, 30, 40, 50]
Stitching Results: foobar
Borrow: <word>
The vector is still available: [a, b]
Consumption: <num>
// ============================================
// Vec,String Common Interaction Patterns with Ownership
// ============================================
fn take_first_word(words: &Vec<String>) -> Option<&str> {
words.first().map(|s| s.as_str())
}
fn remove_and_return(vec: &mut Vec<i32>, index: usize) -> Option<i32> {
if index < vec.len() {
Some(vec.remove(index))
} else {
None
}
}
fn main() {
let mut words = vec![
String::from("hello"),
String::from("rust"),
String::from("world"),
];
println!("Original vector: {:?}", words);
if let Some(first) = take_first_word(&words) {
println!("The First Word: {}", first);
}
let removed = remove_and_return(&mut words, 1);
println!("Remove Index 1: {:?}, Remaining: {:?}", removed, words);
let mut numbers = vec![10, 20, 30, 40, 50];
while let Some(val) = numbers.pop() {
println!("Pop up: {}", val);
}
println!("The vector is empty: {:?}", numbers);
let s1 = String::from("foo");
let s2 = String::from("bar");
let combined = s1 + &s2;
println!("Stitching Results: {}", combined);
let data = vec![String::from("a"), String::from("b")];
for word in &data {
println!("Borrow: {}", word);
}
println!("The vector is still available: {:?}", data);
let data2 = vec![1, 2, 3];
for num in data2 {
println!("Consumption: {}", num);
}
}
Output:
Original vector: ["hello", "rust", "world"]
The First Word: hello
Remove Index 1: Some(20), Remaining: ["hello", "world"]
Pop up: 50
Pop up: 40
Pop up: 30
Pop up: 20
Pop up: 10
The vector is empty: []
Stitching Results: foobar
Borrow: a
Borrow: b
The vector is still available: ["a", "b"]
Consumption: 1
Consumption: 2
Consumption: 3
for item in &vecBorrow traversal (the vector remains available),for item in vecConsume traversal (the vector is moved).+The operator consumes the String on the left.pop()ReturnsOption<T>and consumes the last element.
❓ FAQ
move?.clone() or move?move has zero overhead, while clone requires allocating heap memory.📖 Summary
- Ownership in Rust is based on three core principles: a single owner, automatic destruction at the end of scope, and transferable ownership.
- Data on the stack (scalar types) follow Copy semantics; assignment automatically copies the value.
- Data on the heap (String, Vec) follows move semantics; assignment transfers ownership.
- After relocation, the original variable becomes invalid, and the compiler blocks access—this is a compile-time check
- Function calls and return values also transfer ownership
.clone()Explicit deep copy that preserves the original variable—but at the cost of performance
📝 Exercises
- Difficulty ⭐: Write a program that creates a
String, assigns it to another variable, and then attempts to print the first variable—observe the compiler's error message. - Difficulty ⭐⭐: Write a function
fn append_world(s: String) -> Stringthat appends " world" to the end of a string and returns it. Call it inmainand observe how ownership is passed into and out of the function. - Difficulty ⭐⭐⭐: Define a function
fn calculate_length(s: String) -> (String, usize)that returns a string and its length, while returning ownership of the string. Verify that the original variable can still be used after the function is called.