Introduction
Scopes play an important part in ownership, borrowing, and lifetimes.
That is, they indicate to the compiler when borrows are valid, when resources can be freed, and when variables are created or destroyed.
What’s RAII
Rust enforces RAII, so whenever an object goes out of scope, its destructor is called and its owned resources are freed.
RAII: Resource Acquisition Is Initialization
This behavior shields against resource leak bugs, so you’ll never have to manually free memory or worry about memory leaks again!
Here’s a quick showcase:
1 | fn create_box() { |
memory analyzer:rustc main.rs && valgrind ./main
Destructor
The notion of a destructor in Rust is provided through the Drop trait. The destructor is called when the resource goes out of scope.
This trait is not required to be implemented for every type, only implement it for your type if you require its own destructor logic.
showcase:
1 | struct ToDrop; |
1 | Made a ToDrop! |
Ownership
Because variables are in charge of freeing their own resources, resources can only have one owner.
This also prevents resources from being freed more than once. Note that not all variables own resources (e.g. references).
When doing assignments (let x = y) or passing function arguments by value (foo(x)), the ownership of the resources is transferred. In Rust-speak, this is known as a move.
After moving resources, the previous owner can no longer be used. This avoids creating dangling pointers.
Example:
1 | // This function takes ownership of the heap allocated memory |
可以看到,不论是b,还是a,在调用destroy_box()后,都失去了ownership。
Borrowing
Most of the time, we’d like to access data without taking ownership over it. To accomplish this, Rust uses a borrowing mechanism. Instead of passing objects by value (T), objects can be passed by reference (&T).
Example:
1 | // This function takes ownership of a box and destroys it |
在block里面,调用eat_box_i32()尝试destroy boxed_i32前,boxed_i32已经被_ref_to_i32借用了,而且eat_box_i32()被包含在_ref_to_i32的scope里面,此时,eat_box_i32()试图destroy boxed_i32是不可能的事情。
Mutability
Mutable data can be mutably borrowed using &mut T. This is called a mutable reference and gives read/write access to the borrower.
In contrast, &T borrows the data via an immutable reference, and the borrower can read the data but not modify it:
Example:
1 |
|
Freezing
When data is immutably borrowed, it also freezes. Frozen data can’t be modified via the original object until all references to it go out of scope:
1 | fn main() { |
Aliasing
Data can be immutably borrowed any number of times, but while immutably borrowed, the original data can’t be mutably borrowed. On the other hand, only one mutable borrow is allowed at a time. The original data can be borrowed again only after the mutable reference goes out of scope.
1 | struct Point { x: i32, y: i32, z: i32 } |
在第一个block里面,point已经被borrowed_point借用,此时如果试图进行mutable borrow是禁止的,但是immutable是可以多次的。即,如果一个对象已经被borrow了,都不能再进行mutable borrrow,除非borrow已经结束。
在第二个block里面,point已经被mutable borrow,此时即使是immutable borrow也是不允许的。
The Ref Pattern
When doing pattern matching or destructuring via the let binding, the ref keyword can be used to take references to the fields of a struct/tuple.
The example below shows a few instances where this can be useful:
1 |
|
1 | ref_c1 equals ref_c2: true |