In many languages, variables act like names to assign to pieces of data.
In this example,
arr2 are both aliases that really mean the same list.
This is not how variables work in Rust.
Rust variables have the concept of "ownership". This basically means that a variable owns a piece of data. Another way to think about is it that a piece of data actually lives inside the variable itself.
If we wanted to write the same piece of code in Rust, it wouldn't do the same thing. If we have something like this:
arr, then being moved from
After it's moved, there isn't anything in
arr, so we can't use it.
This is the core idea behind how variables work in Rust. Instead of thinking of variables as a way to name data, think of them as the place where the data is actually stored. If the variable no longer exists (i.e., goes out of scope), then neither does the data.
Now, let's try writing that code in Rust:
But that's about as far as we can go. One of Rust's rules with borrows is that we can't modify data if an immutable reference to it exists. Another big rule is that we can have one mutable reference or as many immutable references as we want, but never both. So, we're stuck. This is one of the things that creates the most frustration when working in Rust. There's always a way forward, but it either requires thinking of our problem differently, or using other tools that Rust provides us. In most cases, we'll have to take the first option, and this case is no exception.
If we wanted to get the same result, we could simply use:
References are extremely useful when dealing with functions. They let us pass our data to a function without actually giving the data to the function (so the function "borrows" it instead). For example:
Mutable references are similar to normal references, but they let us modify the data that they're borrowing. There can only ever be one mutable reference, and it can't exist if there are immutable references.
In general, this is fine. There aren't that many cases where you'd need to give away two mutable references simultaneously. Computers run programs sequentially (one step after another), so you can just create mutable references as needed. In fact, the place where this falls apart is with concurrent programming, where steps aren't executed one after another. There are more language features that can be used to circumvent that, but we'll talk about those later.
We'll go into more depth on lifetimes in the next lesson, but the big idea behind them is that data only lives for a certain amount of time on the computer. Take a look at this code, for example:
This code will not work.
The issue is that we're setting
list_ref to a reference of
list only exists within that block.
As soon as it isn't accessible anymore, it'll get removed from memory,
println wouldn't have anything to access.
In some languages, doing something like this might lead to undefined behavior. Our reference would still point to a place in memory (specifically, the point that the Vec used to take up), but that memory could contain anything at all when we print it. Because this leads to undefined behavior, Rust gives us an error:
This is the basic idea behind lifetimes.
Data only lives for a certain amount of time,
and if references outlive the things they point to, issues start to creep up.
To fix this, we would need to make
list live longer.
An easy way to do that is to move it into a variable that lives longer:
Alternatively, we could just remove the scope. There are also nicer ways to do things like this, which we'll look into soon. Happy coding!