google-site-verification: google61fe8ba583a51912.html
Complete Guide to Rust Ownership

Introduction to Ownership in Rust

Ownership is Rust's most unique and defining feature. It enables Rust to make memory safety guarantees without needing a garbage collector. Understanding ownership is crucial for writing effective Rust code, as it affects how you design your programs and manage resources.

Key Concepts

  • Each value has an owner: Every value in Rust has a single variable that's its "owner"
  • Only one owner at a time: A value can only have one owner at any given time
  • Owner goes out of scope: When the owner goes out of scope, the value is dropped (freed from memory)

These three rules form the foundation of Rust's memory safety guarantees.

1. Basic Ownership Rules

Stack and Heap

fn main() { // Stack-allocated types (Copy types) let x = 5; // i32 stored on stack let y = true; // bool stored on stack let z = 3.14; // f64 stored on stack let arr = [1, 2, 3]; // array stored on stack // Heap-allocated types (Owned types) let s1 = String::from("hello"); // String data stored on heap let v1 = vec![1, 2, 3]; // Vec data stored on heap let b1 = Box::new(42); // Box points to heap // Size of types on stack println!("Size of i32: {} bytes", std::mem::size_of::<i32>()); println!("Size of String: {} bytes", std::mem::size_of::<String>()); println!("Size of Vec<i32>: {} bytes", std::mem::size_of::<Vec<i32>>()); }

Owner Goes Out of Scope

fn main() { { // s is not valid here, it's not yet declared let s = String::from("hello"); // s is valid from this point forward println!("{}", s); // s is valid here } // scope is now over, s is no longer valid // println!("{}", s); // Error: s not found in this scope // Multiple scopes let x = 5; // x is valid from here { let y = 10; // y is valid from here println!("x = {}, y = {}", x, y); // Both valid } // y goes out of scope println!("x = {}", x); // x still valid // println!("y = {}", y); // Error: y not found }

Ownership Transfer (Move)

fn main() { // Move semantics for heap-allocated types let s1 = String::from("hello"); let s2 = s1; // s1 is MOVED to s2 // println!("{}", s1); // Error: s1 no longer valid println!("{}", s2); // OK: s2 owns the string // For stack-allocated types, copy happens instead of move let x = 5; let y = x; // x is COPIED to y println!("x = {}, y = {}", x, y); // Both valid // Move in functions let s = String::from("hello"); take_ownership(s); // s is moved into the function // println!("{}", s); // Error: s no longer valid let x = 5; make_copy(x); // x is copied, so still valid println!("x = {}", x); // OK } fn take_ownership(some_string: String) { println!("{}", some_string); } // some_string goes out of scope and is dropped fn make_copy(some_integer: i32) { println!("{}", some_integer); } // some_integer goes out of scope, but nothing special happens

Return Values and Scope

fn main() { let s1 = gives_ownership(); // gets ownership from function println!("s1 = {}", s1); let s2 = String::from("hello"); let s3 = takes_and_gives_back(s2); // s2 moved, s3 gets ownership // println!("{}", s2); // Error: s2 moved println!("s3 = {}", s3); // Returning multiple values with tuple let s4 = String::from("hello"); let (s5, len) = calculate_length(s4); // s4 moved, returns tuple println!("'{}' length = {}", s5, len); } fn gives_ownership() -> String { let some_string = String::from("yours"); // some_string created some_string // returned, ownership moves out } fn takes_and_gives_back(a_string: String) -> String { a_string // returned, ownership moves out } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); (s, length) // returns tuple with string and length }

2. References and Borrowing

Immutable References

fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // Pass reference println!("Length of '{}' is {}", s1, len); // s1 still valid // Multiple immutable references let s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{}, {}", r1, r2); // OK: multiple immutable refs // References are immutable by default let s = String::from("hello"); let r = &s; // r.push_str(" world"); // Error: cannot mutate through immutable ref } fn calculate_length(s: &String) -> usize { // s is reference to String s.len() } // s goes out of scope, but doesn't drop what it refers to

Mutable References

fn main() { let mut s = String::from("hello"); change(&mut s); // Pass mutable reference println!("{}", s); // "hello, world" // Only one mutable reference allowed at a time let mut s = String::from("hello"); let r1 = &mut s; // let r2 = &mut s; // Error: cannot borrow as mutable more than once println!("{}", r1); // Can't mix mutable and immutable references let mut s = String::from("hello"); let r1 = &s; // immutable borrow let r2 = &s; // immutable borrow - OK // let r3 = &mut s; // Error: cannot borrow as mutable because also borrowed as immutable println!("{}, {}", r1, r2); // References go out of scope at the end of their last usage let mut s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{}, {}", r1, r2); // r1 and r2 no longer used after this let r3 = &mut s; // OK: previous references no longer used println!("{}", r3); } fn change(some_string: &mut String) { some_string.push_str(", world"); }

Dangling References

fn main() { // This function would create a dangling reference // let reference_to_nothing = dangle(); // Error // This works because string is returned directly let s = no_dangle(); println!("{}", s); } // This function tries to return a reference to a value that will be dropped // fn dangle() -> &String { // let s = String::from("hello"); // &s // s will be dropped when function ends // } // Error: returns reference to local variable // Correct version returns the String directly (ownership moved) fn no_dangle() -> String { let s = String::from("hello"); s // Ownership moves out, nothing is dropped }

Rules of References

fn main() { // Rule 1: At any time, you can have either one mutable reference or any number of immutable references let mut s = String::from("hello"); // Many immutable references allowed let r1 = &s; let r2 = &s; println!("{} and {}", r1, r2); // r1 and r2 go out of scope here // Now one mutable reference allowed let r3 = &mut s; println!("{}", r3); // Rule 2: References must always be valid let reference; { let s = String::from("hello"); // reference = &s; // Error: s doesn't live long enough } // s dropped // println!("{}", reference); // This works because string literal has 'static lifetime let s = "hello"; let reference = &s; println!("{}", reference); }

3. The Slice Type

String Slices

fn main() { let s = String::from("hello world"); // Slices are references to parts of a String let hello = &s[0..5]; // "hello" let world = &s[6..11]; // "world" let whole = &s[..]; // "hello world" println!("hello: '{}'", hello); println!("world: '{}'", world); // Shorthand syntax let slice1 = &s[0..2]; // "he" let slice2 = &s[..2]; // "he" - same as above let slice3 = &s[3..]; // "lo world" // Slices are references, so they respect borrowing rules let mut s = String::from("hello"); let slice = &s[0..2]; // s.push_str(" world"); // Error: cannot borrow as mutable while immutable borrowed println!("{}", slice); // slice still valid } fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] // Return whole string if no space found } fn first_word_better(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let mut s = String::from("hello world"); let word = first_word(&s); // word gets reference to part of s // s.clear(); // Error: cannot clear while borrowed println!("first word: {}", word); // More flexible with &str let word = first_word_better(&s[..]); // Works with String slice let word = first_word_better("hello world"); // Works with string literal // String literals are slices let s = "Hello, world!"; // s: &str }

Array Slices

fn main() { let arr = [1, 2, 3, 4, 5]; // Array slices let slice = &arr[1..3]; // [2, 3] println!("{:?}", slice); // Function that works with any slice fn sum_slice(slice: &[i32]) -> i32 { let mut sum = 0; for &item in slice { sum += item; } sum } println!("Sum: {}", sum_slice(&arr)); println!("Sum: {}", sum_slice(&arr[2..4])); // Mutable array slices let mut arr = [1, 2, 3, 4, 5]; let slice = &mut arr[1..4]; for item in slice { *item *= 2; } println!("Modified array: {:?}", arr); // [1, 4, 6, 8, 5] }

4. Ownership and Functions

Function Parameters and Returns

fn main() { let s = String::from("hello"); // Ownership moves into function takes_ownership(s); // println!("{}", s); // Error: s moved let x = 5; // x is copied (implements Copy) makes_copy(x); println!("x = {}", x); // OK // Getting ownership back let s1 = String::from("hello"); let s2 = takes_and_returns(s1); // println!("{}", s1); // Error: s1 moved println!("{}", s2); // OK // Multiple values let s3 = String::from("hello"); let (s4, len) = calculate_length_with_owner(s3); println!("'{}' length = {}", s4, len); } fn takes_ownership(some_string: String) { println!("{}", some_string); } // some_string dropped fn makes_copy(some_integer: i32) { println!("{}", some_integer); } // some_integer goes out of scope, nothing special fn takes_and_returns(some_string: String) -> String { println!("Processing: {}", some_string); some_string // returned, ownership moves out } fn calculate_length_with_owner(s: String) -> (String, usize) { let length = s.len(); (s, length) }

References in Functions

fn main() { let s1 = String::from("hello"); let len = calculate_length_ref(&s1); println!("'{}' length = {}", s1, len); // s1 still valid let mut s2 = String::from("hello"); change(&mut s2); println!("{}", s2); // Multiple parameters with references let s3 = String::from("long string is long"); let s4 = String::from("xyz"); let result = longest(&s3, &s4); println!("Longest: {}", result); } fn calculate_length_ref(s: &String) -> usize { s.len() } fn change(s: &mut String) { s.push_str(", world"); } // Function with multiple references - needs lifetime parameter fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

5. Ownership with Structs

Structs Containing Owned Data

#[derive(Debug)] struct User { username: String, // Owned String email: String, // Owned String sign_in_count: u64, active: bool, } #[derive(Debug)] struct File { name: String, data: Vec<u8>, } impl File { fn new(name: &str) -> Self { File { name: String::from(name), data: Vec::new(), } } fn write(&mut self, data: &[u8]) { self.data.extend_from_slice(data); } fn read(&self) -> &[u8] { &self.data } } fn main() { // Ownership of struct fields let user1 = User { email: String::from("[email protected]"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; // Moving a struct moves all fields let user2 = user1; // user1 moved to user2 // println!("{:?}", user1); // Error: user1 moved // Creating a new user with some moved fields let user3 = User { email: String::from("[email protected]"), ..user2 // user2 partially moved }; // println!("{:?}", user2); // Error: user2 partially moved // Working with File let mut file = File::new("data.txt"); file.write(b"Hello, world!"); println!("File: {:?}", file); // Borrowing struct fields let data = file.read(); println!("Data: {:?}", data); }

Structs with References

// Struct can hold references, but needs lifetime annotation #[derive(Debug)] struct Book<'a> { title: &'a str, // Reference to string slice author: &'a str, // Reference to string slice year: u32, } #[derive(Debug)] struct Excerpt<'a> { content: &'a str, book: Book<'a>, } fn main() { let title = String::from("The Rust Programming Language"); let author = String::from("Steve Klabnik"); let book = Book { title: &title, // Borrow from title author: &author, // Borrow from author year: 2019, }; println!("Book: {:?}", book); { let excerpt_content = String::from("Some excerpt from the book..."); let excerpt = Excerpt { content: &excerpt_content, book: book, // book is moved here }; println!("Excerpt: {:?}", excerpt); // excerpt_content goes out of scope here } // excerpt dropped, but book also dropped because it was moved // println!("{:?}", book); // Error: book moved }

6. Ownership with Enums

Enums with Owned Data

#[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), // Owns a String ChangeColor(i32, i32, i32), } #[derive(Debug)] enum IpAddr { V4(String), // Owns the address string V6(String), // Owns the address string } #[derive(Debug)] enum WebEvent { PageLoad, KeyPress(char), Click { x: i64, y: i64 }, } fn main() { // Ownership moves when enum variants are created let msg1 = Message::Write(String::from("hello")); // Matching consumes ownership unless we use references match msg1 { Message::Write(text) => println!("Message: {}", text), _ => (), } // text dropped here // println!("{:?}", msg1); // Error: msg1 moved // Borrowing in match let msg2 = Message::Write(String::from("world")); match &msg2 { // Borrow msg2 Message::Write(text) => println!("Borrowed: {}", text), _ => (), } println!("Still have: {:?}", msg2); // OK // Vec of enums with owned data let messages = vec![ Message::Write(String::from("first")), Message::Write(String::from("second")), Message::Quit, ]; for msg in &messages { // Borrow to avoid moving if let Message::Write(text) = msg { println!("Text: {}", text); } } println!("Still have messages: {:?}", messages); }

Option and Result Ownership

fn main() { // Option with owned data let s = String::from("hello"); let opt = Some(s); // println!("{}", s); // Error: s moved into opt // Taking ownership from Option let mut opt = Some(String::from("hello")); if let Some(value) = opt { println!("Got: {}", value); // value owns the string } // value dropped // println!("{:?}", opt); // opt is None now? Actually opt was moved // Better: take ownership without moving let mut opt = Some(String::from("hello")); if let Some(ref value) = opt { println!("Borrowed: {}", value); // value is &String } println!("Still have: {:?}", opt); // OK // Result with owned data let result: Result<String, std::io::Error> = Ok(String::from("success")); match &result { Ok(s) => println!("Success: {}", s), Err(e) => println!("Error: {}", e), } println!("Result still: {:?}", result); // OK // Map transforms ownership let result = Ok::<_, std::io::Error>(String::from("hello")); let len = result.map(|s| s.len()); // s moved, returns Result<usize, Error> // println!("{:?}", result); // Error: result moved println!("Length: {:?}", len); }

7. Ownership with Collections

Vectors and Ownership

fn main() { // Vector owns its elements let mut v = Vec::new(); let s1 = String::from("hello"); let s2 = String::from("world"); v.push(s1); // s1 moved into vector v.push(s2); // s2 moved into vector // println!("{}, {}", s1, s2); // Error: both moved println!("Vector: {:?}", v); // Accessing elements - borrowing let v = vec![String::from("hello"), String::from("world")]; let first = &v[0]; // Borrows from vector println!("First: {}", first); println!("Vector still: {:?}", v); // OK // Removing elements returns ownership let mut v = vec![String::from("hello"), String::from("world")]; let removed = v.remove(0); // Ownership moves to removed println!("Removed: {}", removed); println!("Remaining: {:?}", v); // Iterating - by value consumes let v = vec![1, 2, 3]; for i in v { // v consumed println!("{}", i); } // println!("{:?}", v); // Error: v moved // Iterating - by reference doesn't consume let v = vec![1, 2, 3]; for i in &v { println!("{}", i); } println!("Still have: {:?}", v); // OK }

HashMap and Ownership

use std::collections::HashMap; fn main() { let mut scores = HashMap::new(); // Inserting takes ownership of keys and values let team_name = String::from("Blue"); let team_score = 10; scores.insert(team_name, team_score); // team_name moved // println!("{}", team_name); // Error: moved println!("Score still exists: {}", team_score); // i32 is Copy // Getting values - returns reference let score = scores.get("Blue"); // Returns Option<&i32> if let Some(&s) = score { println!("Score: {}", s); } // Entry API let mut map = HashMap::new(); map.insert(String::from("key1"), String::from("value1")); // or_insert returns mutable reference to value let value = map.entry(String::from("key1")).or_insert(String::from("default")); println!("Value: {}", value); // "value1" // value is &mut String value.push_str(" modified"); println!("Map: {:?}", map); // "key1": "value1 modified" }

8. Ownership with Custom Types

Implementing Drop

struct CustomResource { name: String, data: Vec<u8>, } impl CustomResource { fn new(name: &str) -> Self { println!("Creating resource: {}", name); CustomResource { name: String::from(name), data: vec![0; 1024], } } fn use_resource(&self) { println!("Using resource: {}", self.name); } } impl Drop for CustomResource { fn drop(&mut self) { println!("Dropping resource: {}", self.name); // Clean up any resources here self.data.clear(); } } fn main() { { let resource = CustomResource::new("temporary"); resource.use_resource(); } // resource dropped here - Drop called automatically println!("After scope"); // Early drop with std::mem::drop let resource = CustomResource::new("early"); resource.use_resource(); drop(resource); // Explicitly drop early // resource.use_resource(); // Error: resource dropped // Resources dropped in reverse order of creation let a = CustomResource::new("A"); let b = CustomResource::new("B"); let c = CustomResource::new("C"); // Dropped: C, B, A }

Copy and Clone Traits

#[derive(Debug, Clone)] struct Person { name: String, // Not Copy (String doesn't implement Copy) age: u32, // Copy } #[derive(Debug, Clone, Copy)] struct Point { x: i32, y: i32, } fn main() { // Copy types (stack-only) let x = 5; let y = x; // Copy println!("x = {}, y = {}", x, y); // Both valid let p1 = Point { x: 10, y: 20 }; let p2 = p1; // Copy (because Point implements Copy) println!("p1 = {:?}, p2 = {:?}", p1, p2); // Both valid // Clone types (heap data) let s1 = String::from("hello"); let s2 = s1.clone(); // Explicit clone needed println!("s1 = {}, s2 = {}", s1, s2); // Both valid let person1 = Person { name: String::from("Alice"), age: 30, }; let person2 = person1.clone(); // Clone, not copy println!("person1 = {:?}", person1); println!("person2 = {:?}", person2); // Clone is explicit let v1 = vec![1, 2, 3]; let v2 = v1.clone(); println!("v1 = {:?}, v2 = {:?}", v1, v2); }

9. Advanced Ownership Patterns

Reference Counting with Rc

use std::rc::Rc; #[derive(Debug)] enum List { Cons(i32, Rc<List>), Nil, } use List::{Cons, Nil}; #[derive(Debug)] struct GraphNode { value: i32, edges: Vec<Rc<GraphNode>>, // Multiple nodes can share ownership } fn main() { // Rc allows multiple owners let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("Reference count after a: {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); // Increase reference count println!("Reference count after b: {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("Reference count after c: {}", Rc::strong_count(&a)); } // c goes out of scope, reference count decreases println!("Reference count after c: {}", Rc::strong_count(&a)); // Graph with shared ownership let node1 = Rc::new(GraphNode { value: 1, edges: vec![], }); let node2 = Rc::new(GraphNode { value: 2, edges: vec![Rc::clone(&node1)], }); let node3 = Rc::new(GraphNode { value: 3, edges: vec![Rc::clone(&node1), Rc::clone(&node2)], }); println!("Node1 reference count: {}", Rc::strong_count(&node1)); println!("Node2 reference count: {}", Rc::strong_count(&node2)); println!("Node3 reference count: {}", Rc::strong_count(&node3)); }

Interior Mutability with RefCell

use std::cell::RefCell; use std::rc::Rc; #[derive(Debug)] struct MockMessenger { sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> Self { MockMessenger { sent_messages: RefCell::new(vec![]), } } fn send(&self, message: &str) { // Even though self is immutable, we can modify through RefCell self.sent_messages.borrow_mut().push(String::from(message)); } fn message_count(&self) -> usize { self.sent_messages.borrow().len() } } // Combining Rc and RefCell for shared mutable ownership #[derive(Debug)] struct SharedData { value: RefCell<i32>, } fn main() { let messenger = MockMessenger::new(); messenger.send("Hello"); messenger.send("World"); println!("Messages sent: {}", messenger.message_count()); println!("Messages: {:?}", messenger.sent_messages.borrow()); // Rc<RefCell<T>> pattern let shared = Rc::new(SharedData { value: RefCell::new(42), }); let shared1 = Rc::clone(&shared); let shared2 = Rc::clone(&shared); // Modify through one reference *shared.value.borrow_mut() += 10; // See change through another reference println!("Value from shared1: {}", shared1.value.borrow()); println!("Value from shared2: {}", shared2.value.borrow()); // Borrowing rules are enforced at runtime with RefCell let borrow1 = shared.value.borrow(); // let borrow2 = shared.value.borrow_mut(); // This would panic at runtime println!("Borrowed: {}", borrow1); }

Reference Cycles and Weak

use std::rc::{Rc, Weak}; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, // Weak reference to avoid cycles children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); // Strong and weak counts println!("branch strong = {}, weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch)); println!("leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf)); // Upgrade weak to strong temporarily if let Some(parent) = leaf.parent.borrow().upgrade() { println!("leaf's parent = {}", parent.value); } }

10. Common Ownership Patterns

Builder Pattern

#[derive(Debug, Clone)] struct Pizza { name: String, size: String, toppings: Vec<String>, } struct PizzaBuilder { name: String, size: String, toppings: Vec<String>, } impl PizzaBuilder { fn new(name: &str) -> Self { PizzaBuilder { name: name.to_string(), size: "medium".to_string(), toppings: Vec::new(), } } fn size(mut self, size: &str) -> Self { self.size = size.to_string(); self } fn add_topping(mut self, topping: &str) -> Self { self.toppings.push(topping.to_string()); self } fn build(self) -> Pizza { Pizza { name: self.name, size: self.size, toppings: self.toppings, } } } fn main() { let pizza = PizzaBuilder::new("Margherita") .size("large") .add_topping("cheese") .add_topping("tomato") .add_topping("basil") .build(); println!("Pizza: {:?}", pizza); // Pizza owns all its data let Pizza { name, size, toppings } = pizza; println!("Name: {}, Size: {}, Toppings: {:?}", name, size, toppings); }

RAII Pattern

use std::fs::File; use std::io::Write; struct LogFile { file: File, path: String, } impl LogFile { fn new(path: &str) -> std::io::Result<Self> { let file = File::create(path)?; Ok(LogFile { file, path: path.to_string(), }) } fn write_log(&mut self, message: &str) -> std::io::Result<()> { writeln!(self.file, "{}", message) } } impl Drop for LogFile { fn drop(&mut self) { println!("Closing log file: {}", self.path); // File automatically closed when dropped } } struct Mutex<T> { data: T, locked: bool, } impl<T> Mutex<T> { fn new(data: T) -> Self { Mutex { data, locked: false } } fn lock(&mut self) -> MutexGuard<T> { self.locked = true; MutexGuard { mutex: self } } } struct MutexGuard<'a, T> { mutex: &'a mut Mutex<T>, } impl<'a, T> Drop for MutexGuard<'a, T> { fn drop(&mut self) { self.mutex.locked = false; println!("Mutex unlocked"); } } impl<'a, T> std::ops::Deref for MutexGuard<'a, T> { type Target = T; fn deref(&self) -> &T { &self.mutex.data } } impl<'a, T> std::ops::DerefMut for MutexGuard<'a, T> { fn deref_mut(&mut self) -> &mut T { &mut self.mutex.data } } fn main() { // File automatically closed when log goes out of scope { let mut log = LogFile::new("app.log").unwrap(); log.write_log("Application started").unwrap(); log.write_log("Processing data").unwrap(); } // log dropped here // Mutex guard releases lock when dropped let mut mutex = Mutex::new(42); { let mut guard = mutex.lock(); *guard += 10; println!("Value: {}", *guard); } // guard dropped, mutex unlocked println!("Mutex locked: {}", mutex.locked); }

Ownership in Threads

use std::thread; fn main() { let data = vec![1, 2, 3, 4, 5]; // Move ownership to thread let handle = thread::spawn(move || { println!("Data from thread: {:?}", data); data.len() }); // println!("{:?}", data); // Error: data moved let length = handle.join().unwrap(); println!("Thread returned length: {}", length); // Multiple threads with scoped threads let mut data = vec![1, 2, 3, 4, 5]; // Use scoped threads for borrowing thread::scope(|s| { for i in &mut data { s.spawn(move || { *i += 1; println!("Increment: {}", i); }); } }); // All threads complete, data available again println!("Data after threads: {:?}", data); }

11. Common Ownership Questions

When to Use What?

fn main() { // Use owned types (String, Vec<T>) when: // - You need to store data long-term // - Data comes from external source // - You need to modify data let owned_string = String::from("hello"); let owned_vec = vec![1, 2, 3]; // Use references (&str, &[T]) when: // - You're just reading data // - You don't need to store data // - Data comes from an existing owner let borrowed_string: &str = &owned_string; let borrowed_slice: &[i32] = &owned_vec; // Use Rc when: // - Multiple parts of code need to own data // - Data is read-only after creation use std::rc::Rc; let shared = Rc::new(vec![1, 2, 3]); let shared1 = Rc::clone(&shared); let shared2 = Rc::clone(&shared); // Use RefCell when: // - Need interior mutability // - Can guarantee borrowing rules at runtime use std::cell::RefCell; let mutable = RefCell::new(42); *mutable.borrow_mut() += 1; // Use Rc<RefCell<T>> when: // - Need multiple owners with mutability let shared_mutable = Rc::new(RefCell::new(42)); let sm1 = Rc::clone(&shared_mutable); let sm2 = Rc::clone(&shared_mutable); *sm1.borrow_mut() += 10; println!("Value: {}", sm2.borrow()); }

Debugging Ownership Issues

// Common ownership errors and solutions fn main() { // Error 1: Use after move let s = String::from("hello"); let t = s; // println!("{}", s); // Error: value used after move // Solution: Use clone if you need both let s = String::from("hello"); let t = s.clone(); println!("s = {}, t = {}", s, t); // OK // Error 2: Cannot move out of reference let s = String::from("hello"); let r = &s; // let t = *r; // Error: cannot move out of &String // Solution: Clone or use reference let t = r.clone(); println!("r = {}, t = {}", r, t); // OK // Error 3: Cannot borrow as mutable more than once let mut s = String::from("hello"); let r1 = &mut s; // let r2 = &mut s; // Error: second mutable borrow println!("{}", r1); // Solution: Use scopes let mut s = String::from("hello"); { let r1 = &mut s; println!("{}", r1); } let r2 = &mut s; println!("{}", r2); // Error 4: Cannot modify while borrowed let mut s = String::from("hello"); let r = &s; // s.push_str(" world"); // Error: cannot modify while borrowed println!("{}", r); // Solution: Finish using reference first let mut s = String::from("hello"); { let r = &s; println!("{}", r); } s.push_str(" world"); println!("{}", s); }

Conclusion

Ownership is the cornerstone of Rust's memory safety guarantees:

Key Takeaways

  1. Each value has a single owner at any given time
  2. When owner goes out of scope, value is dropped
  3. Ownership can be transferred (moved) to other variables
  4. References allow borrowing without taking ownership
  5. Rules prevent data races and dangling references
  6. Lifetimes ensure references are valid

Ownership Rules Summary

OperationStack Types (Copy)Heap Types (Move)
AssignmentCopyMove
Pass to functionCopyMove
Return from functionCopyMove
Store in structCopyMove

Reference Rules Summary

  • Either: One mutable reference
  • Or: Any number of immutable references
  • But not both: Can't mix mutable and immutable
  • References must always be valid (no dangling)

Best Practices

  1. Use references (borrowing) whenever possible instead of taking ownership
  2. Clone only when necessary - it's expensive
  3. Use Rc for shared ownership of immutable data
  4. Use RefCell for interior mutability when you need to modify through shared references
  5. Design APIs with clear ownership semantics
  6. Leverage the type system to encode ownership at compile time

Ownership is what makes Rust unique - it provides memory safety without garbage collection, enables fearless concurrency, and gives you fine-grained control over resources. While it may seem challenging at first, mastering ownership is key to becoming proficient in Rust.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper