Rust Programming Language: References and Borrowing in Rust

0
Rust's ownership system ensures memory safety and prevents common programming errors like data races. One crucial aspect of ownership is borrowing, which allows you to pass references to values without transferring ownership. This article explores references and borrowing in Rust, delving into their use cases, limitations, and how they contribute to Rust's safety guarantees.

The Challenge with Ownership

Consider the code, where ownership of a `String` is transferred to a function:

Rust
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()
}
The issue here is that ownership of `s1` is moved into `calculate_length`, preventing its use after the function call. To address this, Rust introduces references and borrowing.

Introducing References

Instead of transferring ownership, we can pass a reference to the value. A reference is similar to a pointer, indicating the location of the data without taking ownership. In Rust, references are denoted by the `&` symbol. Here's how the code looks with references:

Rust
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()
}
In this example, `&s1` creates a reference to the value of `s1` without taking ownership. The function `calculate_length` accepts a reference as a parameter, and the reference's scope is limited to the function call.

Immutable References

By default, references in Rust are immutable, meaning the data they point to cannot be modified. Attempting to modify a borrowed value using an immutable reference results in a compilation error:

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

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
The error message indicates that you cannot borrow `some_string` as mutable since it is behind an `&` reference. To enable modifications, we need to use mutable references.

Mutable References

Mutable references, denoted by `&mut`, allow modifications to the borrowed value. However, Rust imposes a rule: you can have at most one mutable reference to a value in a given scope. Trying to create multiple mutable references simultaneously results in a compilation error:

Rust
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;  // Compilation error
This restriction prevents data races, enhancing safety by avoiding simultaneous writes to the same data.

Combining Mutable and Immutable References

Combining mutable and immutable references to the same value is also restricted in Rust. This ensures that no unexpected changes occur while reading data. The code snippet below demonstrates an error when attempting to mix mutable and immutable references:

Rust
let mut s = String::from("hello");

let r1 = &s;      // No problem
let r2 = &s;      // No problem
let r3 = &mut s;   // Compilation error

Dangling References

A common pitfall in languages with pointers is creating dangling references—references to data that no longer exists. Rust prevents dangling references by ensuring that references cannot outlive the data they point to. Consider the following example:

Rust
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // Compilation error
}
The error indicates a missing lifetime specifier, a topic covered later. In essence, Rust ensures that references returned from functions do not point to data that goes out of scope.

Post a Comment

0Comments
Post a Comment (0)