🚀
3. Memory Management

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

1

Each value in Rust has a variable that is its owner

2

There can only be one owner at a time

3

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 drop function 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)
  • s1 is moved to s2 and s1 is 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);
}
  • clone method 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_string takes ownership of s and s is no longer valid
fn main() {
  let x = 5;
 
  makes_copy(x);
 
  println!("{}", x);
}
 
fn makes_copy(some_integer: i32) {
  println!("{}", some_integer);
}
  • some_integer is a copy of x and x is 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_back and then back to s2

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");
}
  • &mut is 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

1

At any given time, you can have either one mutable reference or any number of immutable references

2

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_word returns a slice of s and s is borrowed immutably
  • s.clear tries to borrow s mutably 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[..]
}
 
  • s2 is a string literal of type &str and first_word accepts &str

Slices on Different Types

fn main() {
    let a = [1, 2, 3, 4, 5];
 
    let slice = &a[1..3];
 
    println!("{:?}", slice);
}

© 2024 Driptanil Datta.All rights reserved

Made with Love ❤️

Last updated on Mon Oct 20 2025