Why do you need more ownership?
Usually, we are used to having only one owner per value so that the compiler can automatically free resources when the value leaves scope. However, in some data structures, a node may be referenced simultaneously by multiple other structures—such as a node in a graph structure or part of a shared linked list.
For this scenario, if you only use a single ownership, the compiler will refuse to compile because of ownership transfer, or you have to introduce complex lifecycle annotations to ensure that all references are legal.
Consider a simple example:
- You have a link list
a
, which contains the numbers 5 and 10; then you want to create two other linked listsb
andc
, they all sharea
This sub-link list. - If
Box<T>
to implement linked lists, since ownership will be transferred when moved,a
Cannot be simultaneouslyb
andc
owned, resulting in a compile error.
The core idea of Rc<T>
Rc<T>
Multi-ownership is achieved through reference counting. The basic principle can be analogous to the TV set in the family:
- When the first person enters the room to watch the TV, the TV "turns on", which means creating a
Rc<T>
Example. - When others enter the room, they just need to "increase the reference count" (call
Rc::clone
), the TV remains on. - When a certain audience leaves, the reference count decreases; the TV will be turned off only when the last audience leaves and the reference count drops to 0, and the corresponding data will be released.
useRc<T>
, we do not need to specify which part has the data, but rely on reference count to ensure that as long as there are any part of the data in use, the data will not be cleaned.
Share data with Rc<T>
Here is a useRc<T>
Example, this example demonstrates how to make two linked lists share the same child linked list.
We first define a linked list type, where each node usesRc<List>
To hold the reference to the next node:
use std::rc::Rc; enum List { Cons(i32, Rc<List>), Nil, } use List::{Cons, Nil}; fn main() { // Create a shared linked list a: contains 5 and 10 let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("a Quote count = {}", Rc::strong_count(&a)); // Output 1 // Create a linked list b and share its ownership by cloning a let b = Cons(3, Rc::clone(&a)); println!("a Quote count = {}", Rc::strong_count(&a)); // Output 2 { // Create a linked list c in a new scope, share a let c = Cons(4, Rc::clone(&a)); println!("a Quote count = {}", Rc::strong_count(&a)); // Output 3 // c Reference count will automatically decrease when leaving scope } println!("a Quote count = {}", Rc::strong_count(&a)); // Output 2}
In this example, we first create aRc<List>
Examplea
. Then, by callingRc::clone(&a)
,Willa
The ownership of the link is passed to the link table separatelyb
andc
. It should be noted thatRc::clone
It just increased the reference count and did not make deep copy, so it was very efficient.
By callingRc::strong_count
, we can view the changes in the reference count in the program. whenc
After leaving the scope, the count is automatically reduced by 1 until finally, when all references leave the scope, the reference count returns to zero and the data will be cleaned.
Rc<T> Limitations
AlthoughRc<T>
A convenient multi-ownership mechanism is provided, but it can only be used in single-threaded scenarios. This is because the modification of reference count is not thread-safe. If you need to share data in a multi-threaded environment, you can use something likeArc<T>
(Atomic Reference Count) type, which uses atomic operations internally to ensure multi-thread safety.
in addition,Rc<T>
Only sharing of immutable references is allowed. If modifications are required on shared data, internal variability patterns must be used in combination, such asRc<T>
andRefCell<T>
Combined to check borrowing rules at runtime.
Summarize
- Multiple ownership requirements: In some data structures, a value may be shared by multiple parts, and the traditional single ownership model cannot meet the needs.
-
Reference counting principle:
Rc<T>
Shared data is managed through reference counting, and the data is released only when the last reference leaves the scope. -
Efficient cloning: Called
Rc::clone
It only increases the reference count and does not make deep copies, so it is very efficient. -
limit:
Rc<T>
Suitable for single-threaded environments, and only immutable shared data is allowed; it should be considered when mutable sharing is requiredRefCell<T>
or other solutions.
passRc<T>
,Rust provides us with a simple and secure way to achieve multiple ownership, making the management of shared data more intuitive and efficient. I hope this blog can help you better understand and apply the multi-ownership mechanism in Rust, improving the flexibility and security of your code. Happy coding!
The above is personal experience. I hope you can give you a reference and I hope you can support me more.