Each value in Rust can be owned by only one owner, and when it is assigned to another owner, the original owner can no longer use it. It is this mechanism that makes the Rust language memory safe, eliminating the need for automatic garbage collection or manual release. Ownership is one of the most important features of Rust. Let’s look at a simple example.

fn main() { let s = String::from("hello"); let a = s; // The ownership of the string object "hello" is transferred println! (" {} ", s); / / the error! No longer able to use s}Copy the code

The basic case of ownership transfer shown above is that s can no longer be used because A has acquired ownership of the string. This may be quite different from existing programming languages, and in fact was perfectly true in every language I knew before Rust, but now I understand the magic of doing so.

Why transfer ownership?

Rust locates a system programming language that requires memory security and memory management without runtime overhead.

As an example of the runtime overhead of memory management, Java claims to be a memory safe language because there is no need for programmers to manage memory manually (which is a source of memory insecurity), Java uses automatic garbage collection, and all Java programs run in the Jvm. The Jvm must constantly monitor and traverse the Java object tree as the program runs to identify which variables on the heap are no longer referenced and automatically free those variables when a certain period of time has elapsed.

That is, the Jvm is running a garbage collection process that has nothing to do with the actual program, which is called runtime overhead. Of course, due to the positioning of Java, this overhead can be completely ignored.

C/C++, as a system programming language, is manually managed by the programmer. After malloc, free must be used, otherwise memory leaks will occur and eventually occupy all the memory space of the process. In C++, new and DELETE are the pair of good brothers. This is easy to handle visually, as long as you remember to use it simultaneously, but when things get complicated, it becomes difficult and error-prone. Let’s look at some C code:

#include <stdio.h>
#include <malloc.h>
 
int* func()
{
    int *p = malloc(sizeof(int));
    /*
       do something
    */
    return p;
}
 Copy the code

Who can guarantee that someone outside of this function remembers that the pointer is to the heap, not the stack, and remembers to call free()? It’s hard to say, especially as the situation gets more complicated… Therefore, it is difficult to become a MASTER of C/C++, and even Goole is troubled by it and tries to replace part of THE APPLICATION scenarios of C/C++ with Go. The characteristics of Go are not mentioned here. It is undoubtedly an excellent programming language. It also uses automatic garbage collection, so runtime overhead is unavoidable.

The Java and C/C++ examples above represent two current schools of memory management, both of which have certain pain points, which is why Rust decided to take a completely different approach to memory management by transferring ownership. So let’s get back to how Rust manages memory.

Rust memory management

There is no Auto garbage collection in Rust, and manual administration is not required; the compiler takes care of it at compile time. After a successful compilation, the variable memory reclamation is determined, hardcoded into the binary program, and the program itself will be automatically reclaimed when the collection is due. How can compilers be so smart? The ownership system in Rust has done most of the work. The following describes the various features of ownership systems.

scope

Each variable is valid within a scope. Like most programming languages, {} is considered a scope flag, but when run to}, not only variables on the stack are reclaimed, but memory on the heap is reclaimed as well.

fn func() { let n = 2; Let s = String::new(); // allocate on heap}Copy the code

As above, the space allocated on the heap was recycled, this seems to be very normal, but if s is used as the return value, its scope has changed, it will still be able to eventually in a} (in scope at the end) is released, the scope of guarantee that the variable will be recycled, also avoid like the one above C forget calling free ().

How exactly is it recycled? In Rust, a class is defined using a struct, or you can call it something else instead of a “class.” Each object implements a trait, called a Drop (or “interface” if you’re familiar with Java). A Drop contains a method, Drop (), that is called automatically when any object leaves scope, freeing its memory.

transfer

As mentioned at the beginning of this article, a value can only have one owner, so when assigned to another variable, ownership is transferred and the original owner cannot continue to use it. In Rust, a transfer of ownership is called a move. The assignment at the beginning of this article is a basic example of a transfer of ownership, but let’s look at a slightly more complex one.

fn main() { let s = String::from("hello"); func(s); // The ownership of the string is transferred to the inside of func() let a = s; } fn func(s: String) {println! ("{}", s); // s will be released when it leaves scope}Copy the code

Return at the end of a function is a solution, but not a good one. Borrowing, as we’ll see in a moment, is a good solution to this problem.

Note that the move example uses String::new() or String::from() to create a String object. Why not just use a String like let s = “hello” or something like i32? Because the move rule doesn’t apply to them!

fn main() { let n = 2; // let a = n; println! ("{}", n); // success! There is no problem}Copy the code

This seems to contradict previous theories, but in practice ownership rules apply to all types, except that Rust, in order to reduce programming complexity, makes a copy of the original memory as a new variable when assigning values to primitive types, rather than transferring ownership. That is, in this case a is a separate variable, with separate memory, rather than getting it from n. N also retains the memory of the corresponding value, so it can continue to be used.

The basic types are i32, bool, and so on. To be more specific, Copy is implemented in the trait type Rust. The basic Rust type is already built-in. That is, you can Copy a String object without transferring it when assigning a value.

Attention! S in let s = “hello” is not a primitive type variable, although assignment does not transfer ownership, that is because s is of type &str, which is borrowed rather than copied!

The above assignments and arguments are implicit calls to move. In some cases, the move keyword must be explicitly specified otherwise it will not compile, such as closures, which is a common case. Instead of introducing closures for the sake of length, let’s move quickly to the much-mentioned borrowing that is the last part of this section.

To borrow

Sometimes it is too much trouble to transfer ownership. Just as in reality, I can lend my own things to others and still have ownership. Rust supports borrow, denoting with &, and it is also called “quoting” in some articles, but I don’t think this is good. This is because & is very different from & in C/C++, and compilers call it ‘borrow’ instead of ‘refer’!

Let’s see how we can solve the ownership problem with borrowing.

fn main() { let s = String::from("hello"); let a = &s; println! ("{}", s); // success println! ("{}", a); // success, print "hello" func(&s); // success } fn func(s: &String) { println! ("{}", s); }Copy the code

This code is the basic usage of borrowing. A borrowed S’s memory through & and did not transfer it, but now A has access to S’s space. Rust allows multiple borrowers.

However, owners are not allowed to modify variables or transfer ownership while they are being borrowed! This may seem like a courtesy question, but for memory safety reasons, changing the values will cause the borrowed values to be inconsistent with themselves, causing logic errors, and transferring ownership will inevitably invalidate the borrowed values, therefore, this is not allowed! Let’s try to transfer ownership after funC (& S).

fn main() { let s = String::from("hello"); . func(&s); let b = s; // Error s has been borrowed and cannot be transferred}Copy the code

So far, the borrowing mentioned is immutable borrowing, can also be said to be “read-only borrowing”, the borrower is allowed to have more than one, the borrower is not allowed to modify the value, this is not difficult to understand, if there is a borrower to modify the value, will cause data inconsistency.

But sometimes we do need to change the value! For example, with the familiar swap(), Rust provides mutable borrowing (&mut), which allows the mutable borrower to modify the data if the value itself is mutable (MUT). For simplicity, I’ll use string concatenation as an example.

fn main() {
    let mut s = String::from("hello");
    func(&mut s);
}
 
fn func(s: &mut String) {
    s.push_str(" world");  // s = "hello world"
}
 Copy the code

The func() function modifies s with variable borrowing, but variable borrowing has a very strict restriction that there can only be one variable borrow. During the variable borrowing period, no other borrowing is allowed, including immutable borrowing; During variable borrowing, the owner itself cannot do anything, cannot transfer, cannot modify the value.

In some ways, variable borrowing is no different from transfer. It is equivalent to a temporary transfer of ownership that automatically returns to the original owner when the variable receiving the transfer leaves scope.

At this point, the ownership system of Rust is basically introduced. It is these rules that support the banner of Rust memory security, complete and mutually demonstrated, borrowed from the theory of life, which is reasonable. It has to be said that Rust memory management design is very sophisticated!

Tips

Extract several useful tips from this article:

  • Base variables (implementedCopy) variable assignment does not transfer ownership, but copies it
  • Values are not allowed to be modified while being borrowed
  • Only one variable borrow is allowed, and the owner is not allowed to do anything during the borrow
  • Variable borrowing is equivalent to a temporary transfer of ownership, which is returned to the original owner after the loan is released

To the end.

If reproduced, please indicate the source, thank you