Learn › Rust Programming

Ownership & Borrowing

Understand Rust's ownership rules, references, borrowing, and slices.

Ownership Rules

Ownership is Rust's most unique feature and is the foundation of its memory safety guarantees. The three rules are: each value has exactly one owner, there can only be one owner at a time, and when the owner goes out of scope the value is dropped. These rules are enforced at compile time with zero runtime cost. Understanding ownership is essential because it affects how you structure your programs and pass data between functions.

fn main() {
    // s1 owns the String
    let s1 = String::from("hello");

    // Ownership moves to s2; s1 is no longer valid
    let s2 = s1;
    // println!("{}", s1);  // error: value borrowed after move

    println!("{}", s2);

    // Clone creates a deep copy
    let s3 = s2.clone();
    println!("s2 = {}, s3 = {}", s2, s3);

    // Primitives implement Copy, so they don't move
    let a = 5;
    let b = a;
    println!("a = {}, b = {}", a, b);  // both are valid
}

References and Borrowing

Borrowing allows you to reference a value without taking ownership of it. An immutable reference lets you read the value, and you can have multiple immutable references simultaneously. A mutable reference lets you modify the value, but you can only have one mutable reference at a time and no immutable references can coexist with it. These rules prevent data races at compile time, which is a category of bug that is notoriously difficult to debug in other languages.

fn calculate_length(s: &String) -> usize {
    s.len()
    // s goes out of scope but doesn't drop the value
    // because it doesn't own it
}

fn add_exclamation(s: &mut String) {
    s.push_str("!");
}

fn main() {
    let s1 = String::from("hello");

    // Immutable borrow
    let len = calculate_length(&s1);
    println!("'{}' has length {}", s1, len);

    // Multiple immutable borrows are fine
    let r1 = &s1;
    let r2 = &s1;
    println!("{} and {}", r1, r2);

    // Mutable borrow
    let mut s2 = String::from("hello");
    add_exclamation(&mut s2);
    println!("{}", s2);
}

Slices

A slice is a reference to a contiguous sequence of elements within a collection rather than the whole collection. String slices are the most common type and are written as &str. Slices do not have ownership and they include both a pointer to the data and a length. They are a safe and efficient way to work with portions of data without copying. Array slices work the same way and let you pass parts of an array to functions.

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}

fn sum_slice(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

fn main() {
    let sentence = String::from("hello beautiful world");

    let word = first_word(&sentence);
    println!("First word: {}", word);

    // String slices
    let hello = &sentence[0..5];
    let world = &sentence[16..21];
    println!("{} {}", hello, world);

    // Array slices
    let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let first_half = &numbers[..5];
    let second_half = &numbers[5..];
    println!("Sum of first half: {}", sum_slice(first_half));
    println!("Sum of second half: {}", sum_slice(second_half));
}

← Variables & Mutability · Structs & Enums →