π¦ Rust Master Class - Chapter 3: Rust Ownership
My best friend gave his girlfriend everything. His time, his energy, his best years. When she left, he had nothing. I get it. In Rust, we call that a 'move' β and it's the most important concept you'll learn today.
Ownership is a system of rules in Rust that governs how the program manages memory . Unlike languages that use garbage collection or require manual memory management, Rust uses a set of rules that the compiler checks at compile time to ensure memory safety without runtime overhead .
1. Ownership Rules
Ownership is based on three fundamental principles:
- Every value in Rust has a variable called its owner .
- There can only be one owner at a time .
- When the owner goes out of scope, the value is automatically cleaned up (dropped) .
Rust uses a special function called drop to return memory to the allocator at the end of a variable's scope (indicated by the closing curly bracket }) .
2. Move vs. Copy Semantics
The way data interacts depends on whether it is stored on the stack (fixed size) or the heap (unknown or dynamic size) .
Move Semantics (Heap Data)
When you assign a heap-allocated type, like a String, to another variable, Rust performs a move . Instead of copying the expensive heap data, Rust copies the pointer, length, and capacity stored on the stack . To prevent "double-free" errorsβwhere two variables try to clean up the same memoryβRust invalidates the first variable .
Example of a Move:
// Create a new variable
let text = String::from("hello");
// Create a new variable
let copied_text = text; // Ownership moves from text to copied_text
// Output to console
// println!("{}", text); // Error! text is no longer valid
Copy Semantics (Stack Data)
Types with a known size at compile time (like integers) implement the Copy trait . When these values are assigned to a new variable, they are trivially copied on the stack, and the original variable remains valid . Types that implement Copy include all integer types, Booleans, floating-point types, and characters .
Example of a Copy:
// Create a new variable
let value = 5;
// Create a new variable
let borrowed = value; // value is copied to borrowed; both are valid
// Output to console
println!("value: {}, borrowed: {}", value, borrowed); // Works perfectly
3. Borrowing
Borrowing allows you to use a value without taking ownership of it . This is achieved using references, denoted by the & symbol . Because references do not own the data they point to, the data they refer to will not be dropped when the reference goes out of scope .
Example of Borrowing:
fn main() {
// Create a new variable
let text = String::from("hello");
// Create a new variable
let len = calculate_length(&text); // We pass a reference, borrowing text
}
fn calculate_length(s: &String) -> usize { // s is a reference
s.len()
} // s goes out of scope, but text remains valid in main
4. Lifetimes
Lifetimes are a specialized type of generic that provide the compiler with information about how references relate to one another . Their primary purpose is to validate references, ensuring that a reference does not last longer than the data it points to (preventing "dangling pointers") .
- Syntax: Lifetimes are denoted with an apostrophe followed by a name (e.g.,
'a) . - Outlives Relationship: The notation
'b: 'aindicates that lifetime'blasts at least as long as lifetime'a. - Elision: In many common cases, Rust follows lifetime elision rules to automatically assign lifetimes so that the programmer does not have to write them explicitly . For example, if a function has exactly one input reference, that lifetime is assigned to all output references .
π Download the full PDF: https://drive.google.com/file/d/1EurOKf2pqQQQ3fWyXQPr9q8h4JtLUG5_/view?usp=sharing
Part 3 of the Rust Master Class series β STEM EdTech | Automation Consulting | Rust Tutoring
Top comments (0)