|
| 1 | +--- |
| 2 | +title: Ownership, Borrowing, and Slices in Rust |
| 3 | +--- |
| 4 | + |
| 5 | +In this lesson, we’ll take a closer look at three of Rust’s most important concepts: **ownership**, **borrowing**, and **slices**. |
| 6 | +These are the foundations of Rust’s **memory safety**, allowing it to manage memory **without a garbage collector** and to **prevent data races at compile time**. |
| 7 | + |
| 8 | + |
| 9 | +### 1. Ownership |
| 10 | + |
| 11 | +Ownership is **Rust’s memory management system**. Instead of a garbage collector, Rust tracks **who owns each piece of data** and **frees it automatically** when no longer needed. |
| 12 | + |
| 13 | +Key ideas: |
| 14 | +- No garbage collector. |
| 15 | +- Strict rules, checked during compilation. |
| 16 | +- Violating them causes compilation errors. |
| 17 | + |
| 18 | +**Stack** and **Heap** memory: |
| 19 | + |
| 20 | +- **Stack**: |
| 21 | + - Stores data in **Last-In, First-Out (LIFO)** order. |
| 22 | + - Extremely fast because the memory location is always at the “top of the stack.” |
| 23 | + - Requires values to have a **known, fixed size** at compile time. |
| 24 | + - Automatically freed when the variable goes out of scope. |
| 25 | + |
| 26 | +- **Heap**: |
| 27 | + - Stores **dynamically sized or growable data** (like `String` or `Vec`). |
| 28 | + - Memory must be **requested at runtime** from the allocator. |
| 29 | + - Access is **slower** because you must **follow a pointer** from the stack to the heap. |
| 30 | + - Memory must eventually be freed, which is where **ownership rules** come in. |
| 31 | + |
| 32 | +Rust’s **ownership system** exists to **safely manage heap memory**, automatically cleaning up resources and preventing data races. |
| 33 | + |
| 34 | +### **The 3 Ownership Rules** |
| 35 | + |
| 36 | +1. **Each value in Rust has a single owner** (a variable that “owns” the value). |
| 37 | +2. **When the owner goes out of scope, the value is dropped** (memory freed). |
| 38 | +3. **Ownership can be moved, but not copied by default** (unless the type is `Copy` or you explicitly `clone`). |
| 39 | + |
| 40 | + |
| 41 | +#### **Example:** |
| 42 | + |
| 43 | +```rust |
| 44 | +fn main() { |
| 45 | + let s1 = String::from("hello"); // s1 owns the string |
| 46 | + let s2 = s1; // ownership moves to s2 |
| 47 | + |
| 48 | + // println!("{}", s1); // ERROR: s1 no longer owns the value |
| 49 | + println!("{}", s2); // Works |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +- After `s2 = s1`, **s1 is invalidated** to prevent **double free** errors. |
| 54 | +- When `main` ends, `s2` is dropped, and Rust frees the memory automatically. |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +### **Copy vs Clone** |
| 59 | + |
| 60 | +Rust **treats data differently** depending on **where it lives**: |
| 61 | + |
| 62 | +- **Stack-only data (simple types)** → **Copied automatically** |
| 63 | +- **Heap-allocated data (complex types)** → **Moved by default** |
| 64 | + |
| 65 | + |
| 66 | +#### **1. Copy Types (Stack-Only)** |
| 67 | + |
| 68 | +- Examples: integers (`i32`), booleans (`bool`), characters (`char`), and tuples of `Copy` types. |
| 69 | +- These types are **small and fixed-size**, so Rust **copies them cheaply** instead of moving them. |
| 70 | + |
| 71 | +```rust |
| 72 | +fn main() { |
| 73 | + let x = 5; // i32 is a Copy type |
| 74 | + let y = x; // A new copy of 5 is created on the stack |
| 75 | + |
| 76 | + println!("x = {}, y = {}", x, y); // Both valid |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +Stack values are **duplicated instantly**, so `x` still owns its 5 and `y` has its own 5. |
| 81 | + |
| 82 | + |
| 83 | +#### **2. Move Semantics (Heap Data)** |
| 84 | + |
| 85 | +- Types like `String`, `Vec<T>`, or any custom type **holding heap memory** are **moved by default**. |
| 86 | +- Assigning them **transfers ownership** instead of copying the underlying heap memory (which could be expensive). |
| 87 | + |
| 88 | +```rust |
| 89 | +fn main() { |
| 90 | + let s1 = String::from("hello"); // s1 owns the heap data |
| 91 | + let s2 = s1; // s1 is MOVED into s2 |
| 92 | + |
| 93 | + // println!("{}", s1); // ERROR: s1 is no longer valid |
| 94 | + println!("{}", s2); // Only s2 can be used now |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**Why move instead of copy?** |
| 99 | +- Copying large heap data automatically could be **slow**. |
| 100 | +- Move avoids extra work while still keeping memory safe. |
| 101 | + |
| 102 | + |
| 103 | +#### **3. Clone (Deep Copy)** |
| 104 | + |
| 105 | +- If you **want a real copy of the heap data**, call `.clone()`. |
| 106 | + |
| 107 | +```rust |
| 108 | +fn main() { |
| 109 | + let s1 = String::from("hello"); |
| 110 | + let s2 = s1.clone(); // Copies heap data as well |
| 111 | + |
| 112 | + println!("s1 = {}, s2 = {}", s1, s2); // Both valid |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +- **Move**: only the pointer and metadata are copied; old owner is invalid. (Cheap) |
| 117 | +- **Clone**: heap data is copied too; both owners are valid. (More expensive) |
| 118 | + |
| 119 | + |
| 120 | +Think of **ownership like house keys**: |
| 121 | + |
| 122 | +- **Copy** - Making a **duplicate key** for a small box (cheap and simple). |
| 123 | +- **Move** - Handing your **only key** to someone else (you can’t access it anymore). |
| 124 | +- **Clone** - **Building a whole new house** with its own key (expensive). |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +### 2. Borrowing and References |
| 129 | + |
| 130 | +If we want to **use a value in multiple places** without transferring ownership. |
| 131 | +Rust solves this with **borrowing**, which allows **references** to a value. |
| 132 | + |
| 133 | +- A **reference** is like a **pointer** that guarantees memory safety. |
| 134 | +- Borrowing allows **access without taking ownership**, so the original variable stays valid. |
| 135 | +- **No runtime overhead**: the compiler ensures safety rules. |
| 136 | + |
| 137 | + |
| 138 | +### **Immutable References (`&T`)** |
| 139 | + |
| 140 | +An immutable reference lets you **read data without taking ownership**: |
| 141 | + |
| 142 | +```rust |
| 143 | +fn main() { |
| 144 | + let s = String::from("hello"); |
| 145 | + |
| 146 | + let len = calculate_length(&s); // Borrow immutably |
| 147 | + println!("The length of '{}' is {}.", s, len); // s is still valid |
| 148 | +} |
| 149 | + |
| 150 | +fn calculate_length(s: &String) -> usize { |
| 151 | + s.len() // Can read, cannot modify |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +- `&s` is a **reference** (borrow). |
| 156 | +- The original variable **keeps ownership**. |
| 157 | +- You **cannot modify** through an immutable reference. |
| 158 | + |
| 159 | + |
| 160 | +### **Mutable References (`&mut T`)** |
| 161 | + |
| 162 | +If we want to **modify** a value without transferring ownership, we use **mutable references**: |
| 163 | + |
| 164 | +```rust |
| 165 | +fn main() { |
| 166 | + let mut s = String::from("hello"); |
| 167 | + |
| 168 | + change(&mut s); // Borrow mutably |
| 169 | + println!("{}", s); // Output: hello, world |
| 170 | +} |
| 171 | + |
| 172 | +fn change(some_string: &mut String) { |
| 173 | + some_string.push_str(", world"); |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +- Only **one mutable reference** is allowed at a time. |
| 178 | +- This prevents **data races**, ensuring **safe concurrent access**. |
| 179 | + |
| 180 | + |
| 181 | +### **Borrowing Rules** |
| 182 | + |
| 183 | +1. **You can have either:** |
| 184 | + - Any number of **immutable references** |
| 185 | + - **OR** one **mutable reference** |
| 186 | +2. **References must always be valid** (no dangling pointers). |
| 187 | + |
| 188 | +These rules ensure Rust can **guarantee memory safety** at compile time. |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## 3. Slices |
| 193 | + |
| 194 | +A **slice** is a **reference to part of a collection**. |
| 195 | +Slices let you **work with sub-sections of data without copying**. |
| 196 | + |
| 197 | + |
| 198 | +### **String Slices (`&str`)** |
| 199 | + |
| 200 | +```rust |
| 201 | +fn main() { |
| 202 | + let s = String::from("hello world"); |
| 203 | + |
| 204 | + let hello = &s[0..5]; // Slice of "hello" |
| 205 | + let world = &s[6..11]; // Slice of "world" |
| 206 | + |
| 207 | + println!("{} {}", hello, world); |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +- `&s[start..end]` creates a slice from `start` (inclusive) to `end` (exclusive). |
| 212 | +- `&s[..]` creates a slice of the **entire string**. |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +### **Slices in Functions** |
| 217 | + |
| 218 | +Slices are commonly used to **avoid copying data** when processing collections: |
| 219 | + |
| 220 | +```rust |
| 221 | +fn first_word(s: &str) -> &str { // Accepts &String or string literal |
| 222 | + let bytes = s.as_bytes(); |
| 223 | + |
| 224 | + for (i, &item) in bytes.iter().enumerate() { |
| 225 | + if item == b' ' { |
| 226 | + return &s[..i]; // Slice until first space |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + &s[..] // If no space, return entire string |
| 231 | +} |
| 232 | + |
| 233 | +fn main() { |
| 234 | + let s = String::from("hello world"); |
| 235 | + let word = first_word(&s); |
| 236 | + println!("First word: {}", word); |
| 237 | +} |
| 238 | +``` |
| 239 | + |
| 240 | +- `&str` is already a **string slice**. |
| 241 | +- Returning a slice is **efficient** and avoids extra allocations. |
| 242 | + |
| 243 | +--- |
| 244 | + |
| 245 | +### **Array Slices** |
| 246 | + |
| 247 | +Slices also work with arrays: |
| 248 | + |
| 249 | +```rust |
| 250 | +fn main() { |
| 251 | + let arr = [1, 2, 3, 4, 5]; |
| 252 | + let slice = &arr[1..4]; // Elements 2, 3, 4 |
| 253 | + |
| 254 | + for val in slice { |
| 255 | + println!("{}", val); |
| 256 | + } |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +- Array slices are `&[T]`. |
| 261 | +- They **borrow part of the array** without copying it. |
| 262 | + |
| 263 | +--- |
| 264 | + |
| 265 | +### Exercises |
| 266 | +// to be added later |
0 commit comments