Rust High Performance
上QQ阅读APP看书,第一时间看更新

Mutability, borrowing, and owning

There are also rules about mutability in Rust, that prevent data races between threads. Let's see them:

  • All bindings are immutable by default
  • There can be unlimited immutable borrows of a binding at the same time
  • There can only be one mutable borrow of a binding at most at a given point in time
  • If there is a mutable borrow, no immutable borrows can coexist at a given point in time

They are fairly simple to understand. You can read the contents of a binding from as many places as you would like, but if you want to modify a binding, you must somehow ensure that no readers or other writers exist. This, of course, prevents data races, but makes your coding a bit more troublesome.

Let's see this with a couple of examples. Let's first define these two functions:

fn change_third(slice: &mut [u32]) {
if let Some(item) = slice.get_mut(2) {
*item += 1
}
}

fn print_third(slice: &[u32]) {
if let Some(item) = slice.get(2) {
println!("Third element: {}", item);
}
}

The change_third() function requires a mutable u32 slice that will use to add 1 to the third element if the slice has at least three elements. The second will print that element. You can then use this main() function to test it:

fn main() {
let mut my_vector = vec![73, 55, 33];
print_third(&my_vector);
change_third(&mut my_vector[..]);
print_third(&my_vector);
}

As you can see, since the two functions borrow the vector (one of them mutably and the other one immutably), you can continue using the vector in the main() function. This means that the ownership of the vector is in the main() function.

If we had a function that took ownership of the vector, we wouldn't be able to use it later. Consider changing the change_third() function for this one:

fn change_third(mut slice: Vec<u32>) {
if let Some(item) = slice.get_mut(2) {
*item += 1
}
}

In this case, the function receives the argument and takes ownership of the vector (there is no slicing or referencing on the function declaration). Of course, we will need to change the call to the function:

    change_third(my_vector);

The issue is that the program will no longer compile. After we give the ownership of the vector to the change_third() function, there will no longer be a my_vector variable in the main() function. The error that the Rust compiler shows is really clarifying, and it will even point out where the issue is:

In conclusion, if you need to continue using the variable after using it to call a function, pass it by reference, let the function borrow your variable but not own it. If you don't, and you prefer the new function to have absolute control over the variable (even to drop it), pass it by value. This doesn't apply to Copy types, as we saw in Chapter 1, Common Performance Pitfalls, since, in that case, the whole object gets copied to the new function.

References can be a little difficult to manage though. We sometimes require a structure to have a referenced value, but since the structure won't have ownership of the variable to drop it, it will have to make sure that the owner of the variable doesn't do it while it is still in use. For this, we have lifetimes.