Rust Ownership Model and Function Parameters
Memory Storage and Variable Assignment
Rust manages memory through two primary storage areas:
- Stack: Stores data with known size at compile time. This includes primitive types like integers, floats, booleans, characters, and tuples containing only these types. Stack follows FILO (First In, Last Out) principle.
- Heap: Stores data with dynamic size determined at runtime, such as
Stringobjects.
Stack Variable Assignment
When assigning stack variables, data is simply copied:
let x = 42;
let y = x; // Creates a copy of the value
Heap Variable Assignment
Heap variables store pointers to memory locations. Assignment transefrs ownership rather than copying data.
Ownership Principles and Design Goals
Rust's ownership system addresses memory safety with out garbage collection by enforcing these rules:
- Every value must have an owner
- Only one owner per value at any given time
- Value is automatically deallocated when owner goes out of scope
This design ensures memory safety while maintaining performance suitable for systems programming.
Scope-Based Management
Ownership rules are enforced through well-defined scopes:
fn example() {
let message = String::from("Hello World");
println!("{}", message);
// message is automatically dropped here
}
Moving vs Copying
When heap-allocated values are assigned, ownership moves:
let first = String::from("data");
let second = first;
// println!("{}", first); // This would cause a compile error
Types implementing the Copy trait bypass this behavior:
#[derive(Debug, Copy, Clone)]
enum ChickenType {
FreeRange,
BarnRaised,
Organic,
}
#[derive(Copy, Clone, Debug)]
struct Egg {
weight: u8,
category: ChickenType,
}
fn main() {
let egg1 = Egg { weight: 50, category: ChickenType::FreeRange };
let egg2 = egg1; // Copy occurs due to Copy trait
println!("{:?} {:?}", egg1, egg2); // Both accessible
}
Borrowing Solutions
To avoid ownership transfer complications, Rust provides borrowing mechanisms:
Immutable References
fn main() {
let text = String::from("Reference Example");
display(&text); // Borrow without transferring ownership
display(&text); // Can borrow multiple times
let ref1 = &text;
let ref2 = &text;
println!("{} {}", ref1, ref2);
}
fn display(content: &String) {
println!("{}", content);
}
Mutable References
fn modify_text() {
let mut data = String::from("Initial");
let modifier = &mut data;
modifier.push_str(" - Modified");
let another_modifier = &mut data;
another_modifier.push_str(" - Again");
// println!("{}", modifier); // Would cause compile error
}
Slice References
fn string_slices() {
let phrase = String::from("Hello World Example");
let part1 = &phrase[0..5];
let part2 = &phrase[6..11];
println!("{} {}", part1, part2);
}
fn array_slices() {
let mut nums = [10, 20, 30, 40, 50];
let section: &mut [i32] = &mut nums[1..3];
section[0] = 99;
println!("{:?}", nums); // [10, 99, 30, 40, 50]
}
Function Parameter Behavior
Parameter passing follows ownership rules unless types implement Copy:
use std::ops::{Add, Sub};
#[derive(Debug, Copy, Clone)]
struct Coordinate {
x: i32,
y: i32,
}
impl Add for Coordinate {
type Output = Coordinate;
fn add(self, other: Coordinate) -> Coordinate {
Coordinate {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
impl Sub for Coordinate {
type Output = Coordinate;
fn sub(self, other: Coordinate) -> Coordinate {
Coordinate {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
fn main() {
let point1 = Coordinate { x: 5, y: 10 };
let point2 = Coordinate { x: 15, y: 20 };
let sum = point1 + point2;
let difference = point1 - point2;
// Original values still accessible due to Copy trait
println!("{:?} + {:?} = {:?}", point1, point2, sum);
println!("{:?} - {:?} = {:?}", point1, point2, difference);
}
Key Concepts Summary
- Rust's ownership model unique ensures memory safety without garbage collection
- The three core ownership rules form the foundation for safe memory management
- Borrowing mechanisms enable efficient resource sharing while maintaining safety
- Types implementing
Copytrait behave differently in assignments and function calls - Compiler enforces ownership rules at compile time, preventing runtime errors