A simple conclusion for ownership in Rust
A Simple Conclusion for Ownership in Rust
Warm Up
What's my type?
Before we divi into the ownership, there is a essential problem we have to figure out beforehead: What's the type of the variable we just define? Considering the following code snippet:
1 | fn what_is_my_type() { |
The question looks quiet simple:
variable | type |
---|---|
first_value |
&mut i32 |
second_value |
&32 |
third_value |
&mut i32 |
fourth_value |
&i32 |
fifth_value |
&mut i32 |
sixth_value |
i32 |
However, there are some subtle differences between these six variables, just look at this:
- First_value can't be modified becasue it is a
immutable reference
refer to amutable reference i32
- Third_value can be modified because it is a
`
mutable reference
refer to amutable reference i32
1 | fn the_type_is_not_the_same() { |
Now we can determine the final result:
variable | type |
---|---|
first_value |
immutable reference refer to a
mutable reference i32 |
second_value |
immutable reference refer to a
immutable reference i32 |
third_value |
mutable reference refer to a
mutable reference i32 |
fourth_value |
mutable reference refer to a
immutable reference i32 |
fifth_value |
i32 |
sixth_value |
i32 |
That's kind of confusing result, but I won't elaborate it because this isn't what the article is going to do. So just simplely keep things in mind and keep going. If you are still confusing with it and aspire to figure it out, please refer to this articles:
Type with Pattern Matching
Compiling the following function will generate a typical Error code E0507, how can this be? That's what we called Pattern Matching.
&mut val : &mut String
indicate the function receives a parameter of type&mut String
which is a borrowed value;&mut val
indicate thatval
is aString
so the left-hand-side is euqal to the right-hand-side;- In this case,
val
appears to take the ownership away from&mut String
, which is disallowed in rust.
1 | fn cannot_move_out_of_a_mutable_reference(&mut val: &mut String) { |
Utility
This is a struct which providing impelementation of
Clone
trait
1 |
|
Ownership and Borrowing
In Rust, the ownership concept is a set of rules that applies to all values.These rules dictate that each value in Rust has the following:
- Each value in Rust has a owner responsible for the value;
- Only one owner at a time;
- When the owner goes out of scope, the value will be dropped : mabey
exit the frame of scope for a stack value or calling the
Drop
for a heap value; - Ownership can be borrowing by refenrece
- Any number of immutable reference;
- Any number of immutable reference and exactly one single mutable reference at the same time, this is very trivial so I'd like to elaborate it in the subsequent contents.
the Primary principle of Ownership
Ownership transfer in Rust happens when a value is moved
from one variable or binding to another. This typically occurs in the
following scenarios:
- Assignment for variable with a type does not implement the
Copy
trait, When you assign a value to a new variable, ownership of the value is transferred to the new variable. - Function calls (which is also sort of special assignment) : When you pass a value as an argument to a function, ownership of the value is transferred to the function
- Returning from a function: When a function returns a value, ownership of the value is transferred to the calling code.
Assignment
Assignment for variable with a type does not implement the
Copy
trait will lead to ownership transfer.
1 | fn assignment() { |
here is another more precise example, we defined a simple struct
containts [i32; 3]
, and doesn't implement a
Copy
trait:
1 | fn assigment_with_struct() { |
If you implement the Copy
trait for the struct,
ownership transfer will not occur. Instead, the Copy
trait
will be invoked to create a new struct.
1 | fn assigment_with_copyable_struct() { |
Incidentally, assigment for primitives doesn't require explictly implementation of the Copy trait to avoid ownership transfer.
1 | fn assignment_with_primitive() { |
Borrowing
Borrowing in Rust refers to the mechanism that allows
multiple references to access a value without
transferring ownership. When you borrow a value in Rust, you create a
reference to that value. Reference can be either
immtuable(&T
) or mutable(&mut T
),
Here's are a few important aspects of borrowing in Rust:
- Borrowing rules: Rust's borrowing rules ensure that there is only
one mutable reference (
&mut T
) or any number of immutable references (&T
) to a value in a particular scope. - Borrow checker: The Rust compiler has a borrow checker that analyzes your code and ensures that all borrows are valid and adhere to the borrowing rules. If your code violates these rules, the compiler will raise compilation errors.
- Borrow scopes: The
lifetime
of a borrow is determined by its scope. A borrow cannot outlive the value it refers to. This ensures that borrowed references are always valid and do not become dangling references. - No simultaneous mutable and immutable borrows: Rust does not allow simultaneous mutable and immutable borrows of the same value. This prevents potential data races, as having both mutable and immutable access to a value can lead to undefined behavior.
Move Semantics and Ownership Transfer
1 | fn multiple_immutable_reference_is_allowed() { |
Mutable Reference will invalidate every preceding defined reference
1 | fn mut_reference_will_invalidate_preceding_defined_reference() { |
References must last shorter than their owners
1 | fn references_must_last_shorter_than_their_owns() { |
structs can be passed via move or borrow
One thing we should notice is that VecWithCopy
and
&mut VecWithCopy
is not the same type. That's why
let v4 = v1
will encounter a ownership transfer.You may
want to implement the Copy
trait for
&mut VecWithCopy
, but unfortunately, it is not possible
due to Error
code E0206
1 | fn struct_can_be_passed_via_move_or_borrow() { |
A more complicated example
Assuming that we are going to build an HTTP server, here is the procedure of how the server processes a HTTP request:
--- title: http server --- flowchart BT TcpListener -->|bind| Listener -->|incoming| TcpStream subgraph TcpStream direction LR BufReader -->|process request| TcpStream::write_all end
1 | fn main() { |
Ownership with trait
In Rust, regular values and reference values have significant differences. Let's consider the following situation:
- we have a
trait
called SimpleTrait that does not have any methods. - we have a
function
callednew
that receives atrait
SimpleTrait as an argument; - we have a
struct
calledSimpleStruct
. BothSimpleStruct
and&SimpleStruct
implement the trait SimpleTrait.
When you call the function new
, you will witness
something truly magical: for the same function call, some of them take
ownership while others do not. This is because Rust creates a
corresponding function instance for each generic function during
compilation, using a technique called monomorphization.
1 | fn ownership_with_trait() { |