Borrowing: An important thing about Rust

Andy
3 min readAug 2, 2018

--

This post is for people who just started with Rust and are not quite sure how the borrowing and lifetime stuff works.

TL;DR After something was moved, it’s gone. Only one mutable reference at a time. Copying might be expensive but is required sometimes.

I started a small HTTP-Server to learn a bit more about Rust. While doing the first few steps I always came across the same few errors and somehow managed to hack my way around them. That wasn’t really satisfying.

While re-reading some chapters for the Rust book about borrowing and lifetimes I finally understood it. I’ll try to explain my view on how it works. Maybe it helps you too.

Borrowing & Moving

Whenever you create a variable and use that variable as an argument to a function, you’ll move that variable. After the variable has been moved, you are not allowed to use it again.

fn main() {
let foo = String::from("Hi");
other_function(foo);
// foo is now gone, the ownership was moved
}

However, if we made a copy of our variable, this would work. It would also work if the type of our variable would be just an integer which will be copied by default, instead of being moved.

References

If we don’t want to copy our variables, to save memory, or need it later in our scope, we can use references instead.

fn main() {
let foo = String::from("Hi");
do_something(&foo);
do_more(&foo);
}

You can do this how often you want. The only thing you need to look out for are mutable references. You can only create one mutable reference at a time.

// you cannot do this
let mut foo = String::from("Hi");
let x = &mut foo;
let y = &foo;
// you cannot do this
let mut foo = String::from("Hi");
let x = &mut foo;
let y = &foo;
// but you can do this
let mut foo = String::from("Hi");
do_something(&mut foo);
do_more(&mut foo);

The last one works because the functions will be called one after another. There is no time were two references exist.

Returning a reference

Sometimes I tried to return a reference to a variable I created in the same scope. This, of course, throws an error since you can’t return a reference to something that does not exist anymore. In order for this to work, you’ll need to copy the value.

fn main() {
let string1 = String::from("Hi");
let string2 = String::from("Hello");
let longer = longer_string(&string1, &string2);
println!("Result: {:?}", longer);
}
fn longer_string(a: &String, b: &String) -> String {
if a.len() > b.len() {
a.clone()
} else {
b.clone()
}
}

This will not return a reference, it will return a value of the type String which can then be used in the scope that called this function.

Lifetimes

Lifetimes are needed if you have a function that returns a reference or requires a reference as an argument. Sometimes the compiler will know the lifetimes, but most of the time you’ll have to specify them.

fn main() {
let foo = String::from("Hi");
let c = first_letter(&foo);
println!("{:?}", c);
}
fn first_letter<'a>(bar: &'a String) -> &'a str {
return &bar[0..1];
}

The lifetimes are not required in this example since it is simple and the compiler understands simple stuff. The key part here, however, is that the lifetimes tell the compiler, that the references used in this function will live long enough. In this case as long as ‘a.

fn main() {
let string1 = String::from("Hi");
{
let string2 = String::from("Hello");
let longer = longer_string(&string1, &string2);
println!("Result: {:?}", longer);
}
}
fn longer_string<'a>(a: &'a String, b: &'a String) -> &'a str {
return if a.len() > b.len() { a } else { b };
}

Here, Rust requires the lifetime specification. The arguments come from two different scopes and therefore only the shortest one is a valid lifetime that can be returned.

Conclusion

I skipped some parts to keep this rather short. For more information, you should read the Rust book or take a look at the documentation.

--

--