SoFunction
Updated on 2025-04-09

Detailed explanation of reference loops and memory leaks in Rust

Reference Count vs. Reference Loop

In Rust,Rc<T>Allow multiple owners to share the same data when calledRc::cloneWhen 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 likeListEnumeration, of whichConsThe 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,
        }
    }
}

existmainIn the function, we created twoRc<List>Exampleaandb, and modifyaThe 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(&amp;a));

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&amp;a))));
    println!("a Reference count = {}", Rc::strong_count(&amp;a));
    println!("b Reference count = {}", Rc::strong_count(&amp;b));

    if let Some(link) = () {
        *link.borrow_mut() = Rc::clone(&amp;b);
    }

    // At this time, a and b refer to each other, forming a cycle    println!("a Reference count = {}", Rc::strong_count(&amp;a));
    println!("b Reference count = {}", Rc::strong_count(&amp;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, initiallyaandbThe reference counts are 1 and 1, respectively;aoftailModify to pointbAfter that, the reference count of both nodes increases to 2. whenmainAt the end, even local variablesaandbLeave 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&lt;Weak&lt;Node&gt;&gt;,
    children: RefCell&lt;Vec&lt;Rc&lt;Node&gt;&gt;&gt;,
}

impl Node {
    fn new(value: i32) -&gt; Rc&lt;Node&gt; {
        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(&amp;branch);
        .borrow_mut().push(Rc::clone(&amp;leaf));

        println!("branch of引用计数 = {}, Weak reference count = {}",
            Rc::strong_count(&amp;branch),
            Rc::weak_count(&amp;branch)
        );
        println!("leaf of引用计数 = {}, Weak reference count = {}",
            Rc::strong_count(&amp;leaf),
            Rc::weak_count(&amp;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(&amp;leaf));
}

In this example:

  • We useRc::downgradeCreated a pointerbranchWeak reference toleafofparentField.
  • becauseWeak<T>No strong reference count is added, even ifbranchDestroy after leaving scope,leafIt will not prevent memory recycling.
  • When tryingupgradeGetleafWhen 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.