Ownership, References and Borrowing
Memory Management in Rust: 'The Nightmare Begins'
Code on GitHub: https://github.com/caveofprogramming/rust
I’ve finally got around to tackling the thing that Rust is famous or notorious for: its extremely unusual techniques for managing memory.
This ideas I’ve covered here basically correspond to two pages in the Rust documentation.
At this point I’m feeling Rust seems less complicated than modern C++ and way more complicated than C, but then the point is, C and C++ make it horribly easy to create memory-related bugs, and Rust cuts those down while staying ultra-efficient.
Please bear in mind that this blog covers me learning Rust; I could easily say something that’s incorrect. Check the official Rust site if you need authoritative information.
Ownership
In Rust, every value has an owner. There can only be one owner of a value at a given point, and the memory associated with the value is cleaned up when the owner goes out of scope.
Here’s a simple function that accepts a String
argument and just returns that argument.
We can use the function like this:
Here, the text
variable in the first line, declared with let
, initially owns the string. Then ownership passes to the text
parameter of the function.
If we did not return text
from the function, we would not be able to print the string after the function returns, because the string would get cleaned up when the text
function parameter goes out of scope.
So instead we return text
, then ownership of the string passes back to the calling function (main
, or whatever).
In short, if we pass a string to a function, the function by default gets ownership of the string, invalidating the string when the function returns.
This only happens with values that are allocated on the heap. Integers, for example, are purely allocated on the stack, so they can be passed to functions and the function will just get a copy of the integer. The original value is still valid after the function returns.
Borrowing
It’s possible for function to “borrow” values using references. In this case, ownership stays with the original variable.
However, references are immutable by default and cannot be used to modify values.
So this is a bit like passing a string to a method in Java, where the string is immutable.
This function expects to be passed a reference to a string.
The following code then works fine.
The borrow function never takes ownership of the string, so the string is still valid after the function returns.
Note that we need to specify &
when we pass the string, as well as when we declare the parameter type.
Mutable References
It’s possible to create mutable reference; references that allow you to mutate (change) values.
In this case, we are not allowed to have any other valid references, mutable or immutable, to the value, at that point.
This function expect to receive a mutable reference to a string.
The following code all works.
This is the output:
Notice that the mutable_borrow
function has successfully modified the string.
But wait a minute …. ! Earlier I said that if we create a mutable reference to a value, there can be no other references to that value, not even immutable references. But didn’t we earlier in the code create an immutable reference to the string?
This is resolved by the fact that the scope of a reference goes from when it’s declared to its last use. So the compiler sees that the earlier immutable reference is no longer used when we create the mutable reference, which is what allows us to create the mutable reference.
Scope
Consider the following code.
Here we create a mutable string.
Then we create three immutable references to the string and print the string using all three of those references. That’s fine: we can have as many immutable references as we like.
We can’t then create two immutable references and print the string with both of those, because that would require two mutable references to be in scope at the same time, and you can’t create any other references at all to the value if you have a mutable reference to it.
The second commented-out block also doesn’t work, because there we are trying to create a mutable reference and then print the string using the mutable reference and one of the immutable references.
Again there would then be another reference to the string besides the mutable reference in the same scope, which isn’t allowed.
However, as we can then see, we can perfectly well create a mutable reference to the string as long as we don’t then attempt to use any of the previous references to it. The other references are then all considered out of scope.
It’s complicated, but honestly so far I don’t think it’s as complicated as C++ has become!