- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
? Why Care About These Concepts?
What Can Go Wrong on the Stack?
Example: Returning a reference to a local variable
fn create_ref() -> &i32 {
let number = 10;
&number //
This will cause a compile error
}
Compiler Error:
error[E0515]: cannot return reference to local variable `number`
Correct version: Move the value out
fn create_value() -> i32 {
let number = 10;
number //
Move the value instead of returning a reference
}
? What Can Go Wrong on the Heap?
Example: Reallocation with active immutable borrow
fn main() {
let mut vec = vec![1, 2, 3];
let first = &vec[0]; // Immutable borrow
vec.push(4); //
May cause reallocation
println!("{}", first); // Use after potential reallocation
}
Compiler Error:
error[E0502]: cannot borrow `vec` as mutable because it is also borrowed as immutable
Correct version: Borrow after mutation
fn main() {
let mut vec = vec![1, 2, 3];
vec.push(4); //
Mutate first
let first = &vec[0]; // Borrow afterwards
println!("{}", first);
}
Example: Reference lives longer than the value
fn main() {
let r;
{
let vec = vec![1, 2, 3];
r = &vec[0]; //
`vec` goes out of scope here
}
println!("{}", r); // Use after drop
}
Compiler Error:
error[E0597]: `vec` does not live long enough
Example: Move after borrow
fn main() {
let vec = vec![1, 2, 3];
let r = &vec;
let moved = vec; //
Moving vec while r still exists
println!("{:?}", r);
}
Compiler Error:
error[E0505]: cannot move out of `vec` because it is borrowed
Correct version: Use after move or avoid borrowing before move
fn main() {
let vec = vec![1, 2, 3];
let moved = vec; //
Move without borrowing first
println!("{:?}", moved);
}
? What Is Ownership?
fn main() {
{
let _s = String::from("hello");
// `_s` is dropped here automatically
}
// memory is freed
}
The Problem with Shallow Copies
Rust prevents this with Move Semantics
fn main() {
let vec = vec![1, 2, 3];
let moved = vec; // vec is moved
// println!("{:?}", vec); //
error: use of moved value
}
? Compiler Error:
error[E0382]: borrow of moved value: `vec`
fn main() {
let vec = vec![1, 2, 3];
let cloned = vec.clone(); // deep copy
println!("{:?}", vec); //
ok to use
}
fn main() {
let a = 42;
let b = a; // a is copied, not moved
println!("a = {}, b = {}", a, b); //
both are usable
}
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // p1 is copied, not moved
println!("p1: {}, {}", p1.x, p1.y); //
still valid
}
move occurs because `config` has type `Config`, which does not implement the `Copy` trait
Reliability
These concepts allow the Rust compiler to prevent common resource errors such as:
- Memory leaks
- Dangling pointers
- Double frees
- Accessing uninitialized memory
Convenience
The Rust compiler can automatically free resources using these concepts — no need for manual free or a garbage collector.
Performance
Resource management without a garbage collector enables:
- Faster performance
- Suitability for real-time systems
- These features are enforced by the compiler through compile-time checks.
- You don't usually do something manually — instead, you follow Rust's rules.
- Understanding these concepts is essential to fix compiler error messages during development.
A variable is a name for a memory location holding a value of some type.
Memory can be allocated in three regions:
- Stack: Automatically allocated when a function is called, and freed when the function returns.
- Heap: Requires explicit allocation and deallocation (handled by Rust’s ownership model).
- Static memory: Lives for the entire duration of the program.
Dangling Pointer:
A pointer to memory that has been freed or was never initialized.
Memory Leak:
Memory allocated on the heap is never freed — e.g., forgetting to release memory.
Uninitialized Memory:
Using memory before it has been properly allocated or assigned a value.
Double Free:
Attempting to free the same memory more than once — either on the same variable or on a copy.
Since memory is automatically allocated and freed, there are no memory leaks, uninitialized memory, or double free problems.
The function might return a pointer to a value on the stack, leading to a dangling pointer.
Rust prevents this by simply checking that no such references are returned — see stack-dangling-pointer.rs.
fn create_ref() -> &i32 {
let number = 10;
&number //
}
Compiler Error:
error[E0515]: cannot return reference to local variable `number`
- Don’t return references to local function variables — copy or move the value out of the function.
fn create_value() -> i32 {
let number = 10;
number //
}
? What Can Go Wrong on the Heap?
A reference might be used after the memory was reallocated or freed, leading to a dangling pointer.
The borrow checker prevents the reallocation by not allowing a mutable and other reference at the same time.
An immutable reference is needed to push on a vector and possibly reallocate it — see heap-reallocation-dangling-pointer.rs.
fn main() {
let mut vec = vec![1, 2, 3];
let first = &vec[0]; // Immutable borrow
vec.push(4); //
println!("{}", first); // Use after potential reallocation
}
Compiler Error:
error[E0502]: cannot borrow `vec` as mutable because it is also borrowed as immutable
- Don’t do it, and if you really need a mutable reference paired with other references, use the std::cell module.
fn main() {
let mut vec = vec![1, 2, 3];
vec.push(4); //
let first = &vec[0]; // Borrow afterwards
println!("{}", first);
}
- Rust prevents the use after free case by making sure no reference is used after its lifetime has ended, i.e. the value was dropped — see heap-dropped-dangling-pointer.rs.
fn main() {
let r;
{
let vec = vec![1, 2, 3];
r = &vec[0]; //
}
println!("{}", r); // Use after drop
}
Compiler Error:
error[E0597]: `vec` does not live long enough
- Don’t do it, or if you really run into this problem, you might need shared ownership with the std::rc module.
- The borrow checker also does not allow a value to be moved to another variable that could reallocate or free the memory while there are references — see heap-move-dangling-pointer.rs.
fn main() {
let vec = vec![1, 2, 3];
let r = &vec;
let moved = vec; //
println!("{:?}", r);
}
Compiler Error:
error[E0505]: cannot move out of `vec` because it is borrowed
- Don’t move a value to another variable and then use a reference to it you created before.
fn main() {
let vec = vec![1, 2, 3];
let moved = vec; //
println!("{:?}", moved);
}
? What Is Ownership?
- Rust automatically frees memory, so there are no memory leaks or double free calls.
- Rust does this without a garbage collector.
- The concept is simple: Rust calls a destructor (drop) whenever the lifetime of a value ends, i.e., when the value goes out of scope ({} block ends).
fn main() {
{
let _s = String::from("hello");
// `_s` is dropped here automatically
}
// memory is freed
}
- Some values, like a Vec, contain heap-allocated data.
- A shallow copy (only copying pointer and metadata) would cause a double free error if both copies tried to free the same memory.
- When you assign one variable to another, Rust moves the value instead of copying it (unless it implements Copy).
fn main() {
let vec = vec![1, 2, 3];
let moved = vec; // vec is moved
// println!("{:?}", vec); //
}
? Compiler Error:
error[E0382]: borrow of moved value: `vec`
- Don’t use a value after it was moved.
- If you really need a deep copy, use .clone():
fn main() {
let vec = vec![1, 2, 3];
let cloned = vec.clone(); // deep copy
println!("{:?}", vec); //
}
? Copy vs Move.clone() is often unnecessary and can lead to performance issues if overused.
Primitive types (e.g., i32, bool, char) are copied by default.
- No heap data → no double free risk.
fn main() {
let a = 42;
let b = a; // a is copied, not moved
println!("a = {}, b = {}", a, b); //
}
Types with heap data (e.g., Vec, String) are moved by default.
You can make your own types Copy-able by implementing the Copy trait:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // p1 is copied, not moved
println!("p1: {}, {}", p1.x, p1.y); //
}
- If a type is not Copy, Rust will give an error like:
move occurs because `config` has type `Config`, which does not implement the `Copy` trait
? Summary and Miscellaneous InfoDon’t implement Copy if you can live with using references.
You can also just implement Clone and call .clone() explicitly — see clone-semantics.rs.
Ownership, borrowing, and lifetimes enable the Rust compiler to:
- Detect and prevent memory errors
- Handle memory automatically and safely
Safe Rust guarantees memory safety — no undefined behavior from memory misuse.
You can use unsafe Rust inside an unsafe {} block if needed.
Rust understands how to free memory even in:
- Loops
- if/match clauses
- Iterators
- Partial moves from structs
If your program compiles, you get these memory safety guarantees without a runtime garbage collector.