Memory Management
Ways to manage memory:
1. Garbage Collection:
- Automatically manages memory
- Available in Java, Python, etc.
- No control over memory allocation
- Larger program size
- Slows down the program
2. Manual Memory Management:
- Programmer manages memory
- smaller program size
- Available in C, C++, etc.
- Can lead to memory leaks and dangling pointers
- Error-prone
3. Ownership:
- Rust's way of managing memory
- Ensures memory safety without garbage collector
- No memory leaks or dangling pointers
- Control over memory allocation
- Faster than garbage collection
- Slower write time (fighting with the borrow checker)
Types of Memory
Stack
- fixed-size data stored on the stack
- size is known at compile time
- stack frames are pushed and popped (no need to free memory)
- very fast access
Heap
- dynamic-size data stored on the heap
- size is calculated at runtime and can grow or shrink
- slower access
Ownership Rules
Each value in Rust has a variable that is its owner
There can only be one owner at a time
When the owner goes out of scope, the value will be dropped
https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html (opens in a new tab)
Scope
{ // s is not valid here, it’s not yet declared
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // s goes out of scope, deallocates from memory- When a variable goes out of scope, Rust calls
dropfunction and cleans up the memory
Move
- Rust defaults to moving instead of copying
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}Error: borrow of moved value: s1
$ cargo run
35 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
36 | let s2 = s1;
| -- value moved here
37 |
38 | println!("{}, world!", s1);
| ^^ value borrowed here after move- Move (not shallow copy)
s1is moved tos2ands1is no longer valid
Copy
fn main() {
let x = 5;
let y = x; // x is copied
println!("x = {}, y = {}", x, y);
}- Rust has a copy trait for data types that are stored on the stack
Clone
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}clonemethod that can be used to deep copy
Ownership and Functions
fn main() {
let s = String::from("hello");
takes_ownership(s);
println!("{}", s);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}Error: borrow of moved value: s
some_stringtakes ownership ofsandsis no longer valid
fn main() {
let x = 5;
makes_copy(x);
println!("{}", x);
}
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}some_integeris a copy ofxandxis still valid as it is stored on the stack
fn main() {
let s1 = gives_ownership();
println!("{}", s1);
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string
}- ownership is transferred to
s1
fn main() {
let s1 = String::from("hello");
let s2 = takes_and_gives_back(s1);
println!("{}", s2);
}
fn takes_and_gives_back(a_string: String) -> String {
a_string
}- ownership is transferred to
takes_and_gives_backand then back tos2
References
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}&is a reference to the value- references don't have ownership of the underlying value
&is immutable by default
Mutable References
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}&mutis mutable reference, only one mutable reference is allowed at a time, in particular scope
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}Error: cannot borrow s as mutable more than once at a time
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r3 = &mut s;
println!("{}, {}", r1, r3);
}Error: cannot borrow s as mutable because it is also borrowed as immutable
-
Mutable and immutable references cannot coexist
-
However, immutable references are allowed to coexist
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
let r3 = &mut s;
println!("{}", r3);
}- Immutable references go out of scope after
println! - Mutable reference is allowed after immutable references go out of scope
Dangling References
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}Error: missing lifetime specifier
this function's return type contains a borrowed value, but there is no value for it to be borrowed from
Rules of References
At any given time, you can have either one mutable reference or any number of immutable references
References must always be valid
Slices
fn main() {
let s = String::from("hello");
let slice = &s[0..2];
println!("{}", slice);
}- Slices are references a contiguous sequence of elements in a collection, instead of referencing the whole collection
fn main() {
let s = String::from("hello world");
let word = first_word(&s); // 5
s.clear();
}
fn first_word(s: &String) -> int {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}fn main() {
let mut s = String::from("hello world");
let hello = &s[..5];
let world = &s[6..];
let full = &s[..];
let word = first_word(&s); // hello
s.clear();
println!("{}", word);
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}Error: cannot borrow s as mutable because it is also borrowed as immutable
first_wordreturns a slice ofsandsis borrowed immutablys.cleartries to borrowsmutably causing an error
fn main() {
let mut s = String::from("hello world");
let s2 = "hello world"; // string literal of type `&s`
let word = first_word(s2);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
s2is a string literal of type&strandfirst_wordaccepts&str
Slices on Different Types
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice);
}