Intermediate Exercism • rust

References

Lesson Overview

# Introduction

About

To understand references it is necessary to understand ownership. Every value has a single owner. When its scope is exited, the value is dropped. For example:

fn log(msg: String) { // msg takes ownership of the value passed to it
    let formatted_datetime = String::new();
    // code  to format current datetime snipped
    println!("{msg} at {formatted_datetime}");
} // msg is dropped here

pub fn main() {
    let my_string = "Something happened".to_string();
    log(my_string); // passing my_string transfers ownership of its value to msg
    //println!("{my_string:?}"); // uncommenting this line will error because my_string's value has been dropped while owned by msg
}

A reference represents a borrow of an owned value. Instead of transferring ownership of my_string, we can have log borrow it and then give it back to my_string in main at the end of log.

To do this, we define msg as a reference by prefixing an ampersand. The type &String is read as “reference to a String”.

As log’s argument is now a reference, we need to pass a reference. To get a reference to my_string, prefix it with an ampersand, which is the borrow operator. This lends my_string to log for the duration of that function.

fn log(msg: &String) { //msg is defined as a reference with an ampersand
    let formatted_datetime: String;
    // code  to format current datetime snipped
    println!("{msg} at {formatted_datetime}");
}

pub fn main() {
    let my_string = "Something happened".to_string();
    log(&my_string); // my_string is passed as a reference with an ampersand
    println!("{my_string:?}");
}

References are immutable by default

A value at any given time can have multiple references to it as long as they are all immutable, as shown below

fn main() {
    let fibonacci = vec![1, 1, 2, 3, 5, 8, 13];
    // fibonacci is simultaneously passed by reference three times, which is okay because they are all immutable references
    println!(
        "{:#?}",
        check_shapes(&fibonacci[..=1], &fibonacci[1..=3], &fibonacci[2..],)
    );
}

fn check_shapes(constant: &[u8], linear: &[u8], superlinear: &[u8]) -> (bool, bool, bool) {
    (
        is_constant(constant),
        is_linear(linear),
        is_superlinear(superlinear),
    )
}

// understanding the implementations of the following functions is not necessary for this example
// but are provided should you be interested

fn is_constant(slice: &[u8]) -> bool {
    slice
        .first()
        .map(|first| slice.iter().all(|v| v == first))
        .unwrap_or_default()
}

fn is_linear(slice: &[u8]) -> bool {
    let diffs: Vec<_> = slice
        .windows(2)
        .map(|window| window[1] - window[0])
        .collect();
    is_constant(&diffs)
}

fn is_superlinear(slice: &[u8]) -> bool {
    let diffs: Vec<_> = slice
        .windows(2)
        .map(|window| window[1] - window[0])
        .collect();
    diffs.windows(2).all(|window| window[1] > window[0])
}

References can be dereferenced

Sometimes a reference needs to be dereferenced to access its value.

fn is_meaning_of_life(value: &u8) -> bool {
  value == 42
}
// oops! compiler error: `==` operator is not defined for `&u8`, `u8`

To access the value, the dereference operator (*) is prepended to the reference, as shown below

fn is_meaning_of_life(value: &u8) -> bool {
  *value == 42 // okay, the reference is dereferenced from `&u8` to `u8` which `==` can compare with another `u8`
}

References can be mutable

References can be made mutable by appending mut to the ampersand. as shown below

fn add_five(counter: &mut i32) { //counter is defined as a mutable reference
    *counter += 5; // counter is dereferenced to access its value
}

pub fn main() {
    let mut my_count = 0; // my_count must be defined as mutable
    println!("{my_count:?}");
    add_five(&mut my_count); // my_count is passed as a mutable reference
    println!("{my_count:?}");
}

Prints 0 5

A value at any given time can have either one mutable reference or any number of immutable references to it, as shown below

fn add_five(counter: &mut i32) {
    *counter += 5;
}

pub fn main() {
    let mut my_count = 0;
    println!("{my_count:?}");
    let mut my_count2 = &mut my_count; // first mutable reference to my_count
    add_five(&mut my_count); // this errors because my_count's mutable borrow by my_count2 will be used on the next line
    add_five(my_count2);
    println!("{my_count:?}");
}

This works, because the compiler knows that the mutable borrows do not overlap

fn add_five(counter: &mut i32) {
    *counter += 5;
}

pub fn main() {
    let mut my_count = 0;
    println!("{my_count:?}");
    let mut my_count2 = &mut my_count; // first mutable reference to my_count
    add_five(my_count2); // my_count2's borrow of my_count is not used afer this point
    add_five(&mut my_count); // compiler allows second mutable borrow of my_count
    println!("{my_count:?}"); // because it does not conflict with first mutable borrow
}

Prints 0 10


Originally from Exercism rust concepts