Ownership in Rust
In this lesson, you will learn about ownership, one of the most important and unique concepts in Rust. Ownership is the foundation of Rust’s memory safety without using a garbage collector.
Understanding ownership is essential to writing correct, safe, and efficient Rust programs.
What Is Ownership?
Ownership is a set of rules that determine how memory is managed in Rust. These rules are checked at compile time, not at runtime.
Because of ownership, Rust programs avoid common problems such as:
- Memory leaks
- Dangling pointers
- Double frees
- Data races
Why Does Rust Need Ownership?
Most programming languages manage memory using either:
- Garbage collection, or
- Manual memory management
Rust uses neither. Instead, it enforces ownership rules at compile time, ensuring memory safety with zero runtime cost.
The Three Ownership Rules
Rust’s ownership system follows three simple rules:
- Each value in Rust has an owner
- There can only be one owner at a time
- When the owner goes out of scope, the value is dropped
Ownership and Scope
A variable is valid only within the scope in which it is declared. When the scope ends, Rust automatically frees the memory.
{
let x = 10;
println!("{}", x);
}
// x is no longer valid here
Once the block ends, the value owned by x is dropped.
Ownership with Strings
Ownership rules become more visible when working with heap-allocated data,
such as String.
let s = String::from("Rust");
println!("{}", s);
Here, s owns the string value.
Move Semantics
When you assign a value to another variable, ownership is transferred. This is called a move.
let s1 = String::from("Hello");
let s2 = s1;
// println!("{}", s1); // ERROR
println!("{}", s2);
After the move, s1 is no longer valid.
Rust prevents using it to avoid memory errors.
Ownership and Copy Types
Some data types are copied instead of moved.
These types implement the Copy trait.
Examples include integers, floats, and booleans.
let a = 5;
let b = a;
println!("{}", a);
println!("{}", b);
Both a and b are valid because integers are copied.
Ownership and Functions
Passing a value to a function can also transfer ownership.
fn take_ownership(s: String) {
println!("{}", s);
}
fn main() {
let msg = String::from("Hello");
take_ownership(msg);
// msg is no longer valid here
}
Ownership of msg moves into the function.
Returning Ownership from Functions
Functions can return ownership back to the caller.
fn give_back(s: String) -> String {
s
}
fn main() {
let text = String::from("Rust");
let text = give_back(text);
println!("{}", text);
}
Why Ownership Is Powerful
Ownership guarantees memory safety without runtime overhead.
Although it may feel strict at first, ownership leads to:
- Faster programs
- Safer code
- Fewer bugs
- Better concurrency
📝 Practice Exercises
Exercise 1
Create a string variable and print it inside a block scope.
Exercise 2
Assign a string to another variable and observe ownership behavior.
Exercise 3
Pass a string to a function and try using it after the function call.
Exercise 4
Write a function that takes ownership and returns it back.
✅ Practice Answers
Answer 1
{
let name = String::from("Dataplexa");
println!("{}", name);
}
Answer 2
let a = String::from("Rust");
let b = a;
// a is no longer valid
Answer 3
fn consume(s: String) {
println!("{}", s);
}
Answer 4
fn return_string(s: String) -> String {
s
}
What’s Next?
Now that you understand ownership, the next step is learning how Rust allows temporary access without moving values.
In the next lesson, you will learn about borrowing.