OpenGuild
Published on

Rust Practices with Rustlings - Lifetimes

Chapter 16 - Lifetimes

Exercise 1

// The Rust compiler needs to know how to check whether supplied references are
// valid, so that it can let the programmer know if a reference is at risk of
// going out of scope before it is used. Remember, references are borrows and do
// not own their own data. What if their owner goes out of scope?

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is '{}'", result);
}

Because the return type of longest is a reference to one of the parameters, x or y, so the lifetime of the return value is related to the lifetime of the parameters.
The Rust compiler can’t tell whether the reference being returned refers to x or y, so we need to do that by lifetime annotations.

fn longest<'a'>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Exercise 2

// So if the compiler is just validating the references passed to the annotated
// parameters and the return type, what do we need to change?
//
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is '{}'", result);
}

The lifetime of longest function is the same as the lifetime of the smaller of the two parameters, here is string 2.
So we have 2 ways to achive the requirement:

  • Make the string2 live longer, the same as the println! statement
fn main() {
    let string1 = String::from("long string is long");
    let string2 = String::from("xyz");
    let result;
    {
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is '{}'", result);
}
  • Or move the println statement into the inner scope where the string2 still valid
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
        println!("The longest string is '{}'", result);
    }
}

Exercise 3

// Lifetimes are also needed when structs hold references.

struct Book {
    author: & str,
    title: & str,
}

fn main() {
    let name = String::from("Jill Smith");
    let title = String::from("Fish Flying");
    let book = Book { author: &name, title: &title };

    println!("{} by {}", book.title, book.author);
}

The same as with function, we need to annotate the lifetime of the struct

struct Book<'a> {
    author: &'a str,
    title: &'a str,
}

Conclusion

The 16th chapter of Rustlings - Lifetimes ends here.
TIL:

  • What is lifetimes and how to annotate them

Thanks for reading and please add comments below if you have any questions