Reference Count vs. Reference Loop
In Rust,Rc<T>
Allow multiple owners to share the same data when calledRc::clone
When the internal reference count is increased (strong_count
). The corresponding memory will be released only when the reference count drops to 0.
However, if you create aReference loop, for example, two or more values refer to each other, then the reference count of each value will not drop to 0, resulting in the memory being never recycled. Although this situation will not cause the program to crash, it may exhaust system memory when running for a long time or a large amount of data accumulates.
Example: Create a reference loop using Rc<T> and RefCell<T>
Considering the following code snippet, we define a linked list that looks likeList
Enumeration, of whichCons
The variant not only stores an integer, but also passesRefCell<Rc<List>>
Save a reference to the next node:
enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { List::Cons(_, tail) => Some(tail), List::Nil => None, } } }
existmain
In the function, we created twoRc<List>
Examplea
andb
, and modifya
The saved pointer lets it point tob
, thus forming a circular reference:
fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a Reference count = {}", Rc::strong_count(&a)); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); println!("a Reference count = {}", Rc::strong_count(&a)); println!("b Reference count = {}", Rc::strong_count(&b)); if let Some(link) = () { *link.borrow_mut() = Rc::clone(&b); } // At this time, a and b refer to each other, forming a cycle println!("a Reference count = {}", Rc::strong_count(&a)); println!("b Reference count = {}", Rc::strong_count(&b)); // If you try to print the entire list here, the stack overflow will be caused by infinite loops. // println!("a = {:?}", a); }
In this code, initiallya
andb
The reference counts are 1 and 1, respectively;a
oftail
Modify to pointb
After that, the reference count of both nodes increases to 2. whenmain
At the end, even local variablesa
andb
Leave scope, but due to mutual references, their internal reference count is still greater than 0, causing memory to be unfreed.
Solution
Use weak references (Weak<T>):
To solve the reference loop problem, Rust providesWeak<T>
type. andRc<T>
different,Weak<T>
It does not express ownership, its existence will not increase the reference count, nor will it prevent the release of the value.
Application scenario: Tree structure
In a tree structure, the parent node usually has children, and the child node may also need to refer to the parent node. If usingRc<T>
Establishing two-way references will cause circular reference problems. The solution is to let the child nodes passWeak<T>
To refer to the parent node, even if the parent node and the child node refer to each other, there are only all strong references (Rc<T>
) can only be properly destroyed when it is released.
Here is a simple example showing how to use weak references in a node structure to avoid circular references:
use std::rc::{Rc, Weak}; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } impl Node { fn new(value: i32) -> Rc<Node> { Rc::new(Node { value, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }) } } fn main() { // Create a leaf node without a parent node let leaf = Node::new(3); println!("leaf of parent = {:?}", ().upgrade()); { // Create a branch node in the internal scope, taking the leaf node as its child node let branch = Node::new(5); *.borrow_mut() = Rc::downgrade(&branch); .borrow_mut().push(Rc::clone(&leaf)); println!("branch of引用计数 = {}, Weak reference count = {}", Rc::strong_count(&branch), Rc::weak_count(&branch) ); println!("leaf of引用计数 = {}, Weak reference count = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf) ); } // At this time, branch has left the scope and is released. The parent of leaf is upgraded to None println!("leaf of parent = {:?}", ().upgrade()); println!("leaf of引用计数 = {}", Rc::strong_count(&leaf)); }
In this example:
- We use
Rc::downgrade
Created a pointerbranch
Weak reference toleaf
ofparent
Field. - because
Weak<T>
No strong reference count is added, even ifbranch
Destroy after leaving scope,leaf
It will not prevent memory recycling. - When trying
upgrade
Getleaf
When the parent node isRc<Node>
Destroyed, will returnNone
。
This design makes the relationship between the parent and child nodes more in line with the actual ownership semantics: the parent node owns the child node, and the child node only holds a "non-ownership" reference to the parent node, thus avoiding reference loops and potential memory leaks.
Summarize
In this article, we discuss how to use it in RustRc<T>
andRefCell<T>
Create reference loops, and how such loops cause memory leaks. While Rust's memory security guarantees prevent common problems such as dangling pointers, reference loops can still cause memory leaks quietly. To solve this problem, we introducedWeak<T>
Types allow us to avoid circular reference problems in scenarios where bidirectional references are required (such as parent-child relationships in tree structures).
Understand and master these smart pointers (Box<T>
、Rc<T>
、RefCell<T>
andWeak<T>
) nuances are essential for writing efficient and memory-safe Rust programs.
The above is personal experience. I hope this blog will help you understand the reference counting and memory management mechanisms in Rust more deeply, and avoid potential memory leaks in future projects. I also hope everyone supports me.