In Rust, understanding references and borrowing is crucial for managing memory safely and efficiently. This topic will cover the following key concepts:
- References
- Borrowing
- Mutable and Immutable References
- Rules of References and Borrowing
- Practical Examples
- Exercises
- References
A reference is a way to refer to some value without taking ownership of it. References are denoted by the &
symbol.
Example:
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() }
Explanation:
&s1
creates a reference tos1
that is passed to thecalculate_length
function.- The function
calculate_length
takes a reference to aString
as a parameter. - The reference allows the function to read the value without taking ownership.
- Borrowing
Borrowing is the act of creating a reference to a value. When you borrow a value, you do not own it, and thus, you cannot modify it unless the reference is mutable.
- Mutable and Immutable References
Immutable References:
You can have multiple immutable references to a value.
Example:
fn main() { let s1 = String::from("hello"); let r1 = &s1; let r2 = &s1; println!("{} and {}", r1, r2); }
Mutable References:
You can have only one mutable reference to a value at a time.
Example:
fn main() { let mut s1 = String::from("hello"); let r1 = &mut s1; r1.push_str(", world"); println!("{}", r1); }
Explanation:
&mut s1
creates a mutable reference tos1
.- The mutable reference allows modifying the value it points to.
- Rules of References and Borrowing
- At any given time, you can have either one mutable reference or any number of immutable references.
- References must always be valid.
Example of Invalid Code:
fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem let r3 = &mut s; // BIG PROBLEM println!("{}, {}, and {}", r1, r2, r3); }
Explanation:
- The code above will not compile because it tries to create a mutable reference while immutable references are still in use.
- Practical Examples
Example 1: Multiple Immutable References
fn main() { let s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{} and {}", r1, r2); }
Example 2: Mutable Reference
fn main() { let mut s = String::from("hello"); let r1 = &mut s; r1.push_str(", world"); println!("{}", r1); }
Example 3: Combining Mutable and Immutable References
fn main() { let mut s = String::from("hello"); { let r1 = &s; // no problem let r2 = &s; // no problem println!("{} and {}", r1, r2); } // r1 and r2 go out of scope here let r3 = &mut s; // no problem r3.push_str(", world"); println!("{}", r3); }
- Exercises
Exercise 1:
Write a function first_word
that takes a string slice and returns the first word in the string. Use references and borrowing.
fn main() { let s = String::from("hello world"); let word = first_word(&s); println!("The first word is: {}", word); } fn first_word(s: &str) -> &str { // Your code here }
Solution:
fn main() { let s = String::from("hello world"); let word = first_word(&s); println!("The first word is: {}", word); } fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
Explanation:
- The function
first_word
takes a string slices
and returns a string slice. - It iterates over the bytes of the string and returns a slice from the start to the first space.
Conclusion
In this section, we covered the concepts of references and borrowing in Rust. We learned about immutable and mutable references, the rules governing them, and saw practical examples. Understanding these concepts is crucial for writing safe and efficient Rust code. In the next section, we will delve into slices, which are a view into a sequence of elements in a collection.