Rust Borrowing

What Is Borrowing in Rust?

Borrowing allows you to access a value without taking ownership of it. Instead of transferring ownership, Rust provides references (&) that let you “borrow” a value temporarily. Borrowing is particularly useful when you want to read or modify a value without creating a copy or moving ownership.

There are two types of borrowing in Rust:

  1. Immutable Borrowing: Accessing a value without modifying it.
  2. Mutable Borrowing: Accessing and modifying a value.

Immutable Borrowing

Immutable borrowing allows you to access a value without changing it. You create an immutable reference using the & symbol.

Example 1: Immutable Borrowing

fn main() {
let s = String::from("Rust");
let len = calculate_length(&s); // Borrowing s as an immutable reference
println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: &String) -> usize {
s.len() // Accessing the borrowed value
}

Output:

The length of 'Rust' is 4.

Explanation:

  • The function calculate_length borrows the string s as an immutable reference.
  • The original value s remains valid after the borrowing.

Rules of Immutable Borrowing

  1. You can have multiple immutable references to a value at the same time.
  2. You cannot modify the borrowed value through an immutable reference.

Example 2: Multiple Immutable References

fn main() {
let s = String::from("Rust");
let r1 = &s; // Immutable reference 1
let r2 = &s; // Immutable reference 2
println!("r1: {}, r2: {}", r1, r2); // Both references are valid
}

Mutable Borrowing

Mutable borrowing allows you to modify the borrowed value. You create a mutable reference using the &mut symbol.

Example 3: Mutable Borrowing

fn main() {
let mut s = String::from("Rust");
modify(&mut s); // Borrowing s as a mutable reference
println!("Modified string: {}", s);
}

fn modify(s: &mut String) {
s.push_str(" Programming!"); // Modifying the borrowed value
}

Output:

Modified string: Rust Programming!

Explanation:

  • The function modify borrows the string s as a mutable reference and modifies it.
  • The changes persist after the function call.

Rules of Mutable Borrowing

  1. You can have only one mutable reference to a value at a time.
  2. You cannot mix mutable and immutable references to the same value.

Example 4: Mutable Borrowing Restrictions

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

let r1 = &mut s; // Mutable reference
// let r2 = &s; // Error: Cannot borrow as immutable while mutable borrow exists
println!("{}", r1);

// Mutable references must end before creating a new one
let r2 = &mut s; // Valid now, as r1 is no longer used
println!("{}", r2);
}

Borrowing in Functions

Functions can borrow values as arguments, avoiding ownership transfer. This allows the caller to retain ownership while the function operates on borrowed data.

Example 5: Borrowing in Functions

fn main() {
let mut s = String::from("Hello");
print_length(&s); // Immutable borrowing
append_world(&mut s); // Mutable borrowing
println!("Modified string: {}", s);
}

fn print_length(s: &String) {
println!("Length: {}", s.len());
}

fn append_world(s: &mut String) {
s.push_str(", world!");
}

Output:

Length: 5
Modified string: Hello, world!

Borrowing and Scopes

Borrowing follows strict scoping rules. A reference becomes invalid as soon as it goes out of scope. This ensures memory safety.

Example 6: Borrowing and Scoping

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

{
let r = &mut s; // Mutable reference exists in this scope
r.push_str(" is awesome!");
} // r goes out of scope here, allowing new references

let r2 = &s; // Immutable reference is now allowed
println!("{}", r2);
}

Dangling References

Rust prevents dangling references by ensuring that references are always valid. A dangling reference occurs when a reference points to a memory location that has been freed. Rust’s compiler catches such errors at compile time.

Example 7: Dangling Reference Prevention

fn main() {
let r = dangling_reference();
println!("{}", r); // Compiler error
}

fn dangling_reference() -> &String {
let s = String::from("Rust");
&s // Error: s goes out of scope here, making the reference invalid
}

Borrowing in Loops

Borrowing is often used in loops to iterate over collections without consuming them.

Example 8: Borrowing in Loops

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().sum(); // Borrowing values in the vector
println!("Sum: {}", sum);
}

Leave a Comment

BoxofLearn