Rust – What is Borrowing?

Borrowing is one of the central concepts in Rust's memory model and is closely linked to the ownership system. Instead of moving or copying data, data can be borrowed, enabling safe and efficient memory management.

Bernd Fischer

Bernd Fischer

14. Oct 2024 · 3 min read

Rust – What is Borrowing?

Borrowing is a powerful concept in Rust that allows data to be safely borrowed without transferring ownership. It enables efficient memory management while ensuring that no invalid references occur. The distinction between immutable and mutable borrowing helps guarantee data consistency and prevent data races.

1. The Ownership System and Borrowing

In Rust, every variable owns its memory by default. This means that when a variable is created, it takes "ownership" of the data. However, sometimes one only wants a reference to the data of another variable without taking ownership. This is called borrowing.

Example: Borrowing with References

fn main() {
    let s1 = String::from("Hello");
    let s2 = &s1; // s2 "leiht" sich den Wert von s1
    println!("{}, {}", s1, s2);
}

In this example, a reference (&s1) is used to enable borrowing. This means that s2 is an immutable reference to s1 but does not own the data.

2. Immutable Borrowing

Rust distinguishes between immutable and mutable borrowing. When an immutable reference (&T) is used, the value may not be changed as long as this reference is active.

Example: Immutable Reference

fn main() {
    let s = String::from("Rust");
    let r = &s; // unveränderliches Borrowing
    println!("Referenz: {}", r);
}

Here, s cannot be modified as long as the reference r exists. Immutable references are allowed multiple times as long as the value is not changed.

3. Mutable Borrowing

A mutable borrowing allows the value to be changed. However, Rust only permits one mutable reference at a time. This rule prevents data races and ensures that only one place can modify the value.

Example: Mutable Borrowing

fn main() {
    let mut s = String::from("Hello");
    let r = &mut s; // veränderliches Borrowing
    r.push_str(", world!"); 
    println!("Verändert: {}", r);
}

In this example, r can change the value of s. Note that s must be declared as mut to obtain a mutable reference.

4. Rules for Borrowing

Rust has clear rules for borrowing to ensure that memory errors do not occur:

  1. Multiple immutable references are allowed, but there may be only one mutable reference.
  2. Immutable and mutable references may not exist simultaneously.

Example: Rule Violation

fn main() {
    let mut s = String::from("Rust");
    let r1 = &s; // unveränderliches Borrowing
    let r2 = &mut s; // veränderliches Borrowing - Fehler!
    println!("{}, {}", r1, r2);
}

In this case, a compile-time error occurs because immutable and mutable references may not coexist.

5. Lifetimes and Borrowing

To ensure that a reference does not live longer than the value it points to, Rust uses lifetimes. Lifetimes guarantee that no invalid references exist.

Example: Lifetime of a Reference

fn main() {
    let s1 = String::from("Hello");
    let r;
    {
        let s2 = String::from("World");
        r = &s2; // Fehler: s2 lebt nicht lange genug
    }
    println!("Referenz: {}", r);
}

This code produces an error because s2 is declared in an inner block and no longer exists after that block. Rust prevents r from pointing to an invalid reference.