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