The core concept of Rust is
Ownership, a GC-enabled language that allows the runtime to sweep the heap at runtime and release unreferenced garbage objects, such as go. For languages like c/c++, you need to manage the allocation and release of memory yourself.
Rust uses the concept of
Ownership and appends various check rules to the compiler to implement memory management. Note that most of Rust’s work is done at compile time, so there is no additional overhead at runtime. Here are three principles.
- each value, has an owner
- At the same time, a value can have only one owner
- When the owner leaves the scope, the corresponding value is automatically droppe
Let’s first look at leaving the scope and releasing it automatically.
The simplest code, just one line, allocates the string on the heap and disassembles it to observe how memory is managed.
The assembly on line 2 calls
core::convert::From to create the string variable. Then on line 3, at the end of main,
core::ptr::drop_in_place is called automatically to free the string.
The automatic destructuring out of scope is much like c++’s
RAII (Resource Acquisition Is Initialization), except that rust calls
drop trait out of scope.
This code will definitely work with go, but not with rust.
The execution reports an error because the ownership of the s1 value has been moved to s2, and the original s1 is no longer available. move occurs because
s1 has type
String , which does not implement the
You can see the result of the string, just like go, the string header has pointer pointing to heap memory. If s1, s2 are shallow copies and the pointer points to data on the heap, then the same piece of memory will be freed twice after leaving scope!
You can call
s1.clone() to make a deep copy to solve this problem. But it is inefficient to make a memory copy every time, so the concept of
reference references will be introduced later.
Ownership and functions
This is an example from the website. When
takes_ownership is executed, the ownership of the value corresponding to s is transferred to the function and released when it leaves the scope, so if the main function tries to use it again, it will report an error.
But at the same time, x is an int value, and the
makes_copy function will copy this value, not transfer ownership.
In Rust, if the type does not implement
Copy, then the type is assigned, passed, and returned with
Move semantics, which is a bit awkward and not intuitive.
The previous string is a
Move, so let’s look at an integer example.
Here s1 is the i32 type by default, which implements the
Copy semantics, so s1 can be used afterwards.
To see which types implement
Copy semantics by default, they are generally scalars, i.e. types allocated on the stack and sized at compile time.
- All the integer types, such as u32.
- The Boolean type, bool, with values true and false.
- All the floating point types, such as f64.
- The character type, char.
- Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.
So how is it handled for custom types, such as struct?
As you can see, the error is reported because d has been moved when
test is called, so the variable d can no longer be used in main.
But the problem is that struct Data members are all integers, and they all implement
Copy by default.
Here you need to mark the struct
#[derive(Copy, Clone)] , so that the custom type automatically implements the
Copy trait, which requires that all fields have already implemented
struct S2 , since S2 has String type in its field, String type does not implement
Copy trait, so S2 type cannot implement
S2 also contains E1 type, which does not implement
Copy trait, but we can implement
Clone trait for S2 type ourselves, and generate a new E1 instance in
Clone::clone method, which can clone a new S2 instance.