Daily Learnings: Wed, Dec 20, 2023
If you aren’t going all the way, why go at all? — Joe Namath
More Rust Learnings - Borrowing & References
I was able to get some more time during lunch today to continue my rust study from the very comprehensive freeCodeCamp YouTube course found here. I’m still only about 2.5hrs in, with 11.5hrs to go…
I found this topic really interesting as the Rust application that I’ve created uses references a lot without me realizing exactly what it was. Given that a good portion of the application was generated by Github Copilot / ChatGPT, I’m happy to better understand what the AI was actually suggesting.
- Borrowing in Rust: Way of temporarily accessing data without taking full ownership of it
- You take a reference or pointer to the data
- Data can be borrowed mutably or immutably (immutable unless explicitly expressed otherwise)
- The compiler enforces several rules when borrowing to avoid dangling references and data races
- References to data are denoted with the
&character
Rules of References
- At any given time you can have either 1 mutable reference OR any number of immutable references
- References must always be valid, and cannot be dangling pointers to a piece of data that has already been dropped
Valid Borrowing Code Examples
fn main() {
// Example of a valid, immutable reference
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
// Example of valid, mutable reference
let mut s = String::from("hello");
change(&mut s);
// One last example
let x: i32 = 5;
let p: &i32 = &x;
println!("The memory address of x is {:p}", p);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
fn change(s: &mut String) {
some_string.push_str(", world");
}
Invalid Code Examples - Violation of Rule 1
Example of violation of the first rule of borrowing: You can only have 1 mutable reference at any given time.
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
The fix for the above could be something like this:
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
}
// At this point r1 is dropped and no longer active
let r2 = &mut s;
println!("{}", r2);
}
Example of violation of the first rule of borrowing: You can have EITHER 1 mutable reference or multiple immutable references to data, but not both at the same time.
fn main() {
let mut s = String::from("hello");
let r1 = &s; // No prob
let r2 = &s; // No prob
let r3 = &mut s; // PROBLEM!
println!("{}, {}, and {}", r1, r2, r3);
}
This could be fixed like this:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// At this point variables r1 & r2 are dropped as they're no longer used in the program
// So we can create a new, mutable reference
let r3 = &mut s;
println!("{}", r3);
}
Invalid Code Example - Violation of Rule 2
Example of violation of the second rule of borrowing: All references must be valid and not pointers to dropped data.
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
Instead, you might do something like this:
fn main() {
let s: String = no_dangle();
}
fn no_dangle() -> String {
let s = String::from("hello");
s
}