You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust双向链表Debug打印触发栈溢出问题排查

问题原因

自动派生的Debug实现会递归遍历双向链表的节点引用,而双向链表中节点的prev和next字段形成了循环引用(比如头节点的next指向后续节点,后续节点的prev又指回头节点,同时链表的tail字段还引用着尾节点)。当println!("{:?}", list)触发Debug打印时,会无限递归遍历这些循环引用,最终耗尽栈空间导致栈溢出。

解决方案

手动实现Debug trait,只打印链表的元素序列,避免递归遍历节点的双向引用:

方法1:实现迭代器后打印元素

use std::fmt;
use std::rc::Rc;
use std::cell::RefCell;

type Link<T> = Option<Rc<RefCell<Node<T>>>>;

pub struct List<T> {
    head: Link<T>,
    tail: Link<T>,
}

struct Node<T> {
    elem: T,
    next: Link<T>,
    prev: Link<T>,
}

impl<T: fmt::Debug> fmt::Debug for List<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_list()
            .entries(self.iter())
            .finish()
    }
}

impl<T> List<T> {
    pub fn iter(&self) -> Iter<T> {
        Iter {
            next: self.head.as_ref().map(|node| node.borrow()),
        }
    }

    pub fn push_front(&mut self, elem: T) {
        let new_head = Rc::new(RefCell::new(Node {
            elem,
            next: self.head.take(),
            prev: None,
        }));

        match &mut self.head {
            Some(old_head) => {
                old_head.borrow_mut().prev = Some(new_head.clone());
            }
            None => {
                self.tail = Some(new_head.clone());
            }
        }

        self.head = Some(new_head);
    }
}

pub struct Iter<'a, T> {
    next: Option<std::cell::Ref<'a, Node<T>>>,
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        self.next.take().map(|node| {
            self.next = node.next.as_ref().map(|next_node| next_node.borrow());
            &node.elem
        })
    }
}

方法2:手动遍历节点打印元素

如果不想实现迭代器,也可以直接在Debug实现中遍历节点并打印元素:

use std::fmt;
use std::rc::Rc;
use std::cell::RefCell;

type Link<T> = Option<Rc<RefCell<Node<T>>>>;

pub struct List<T> {
    head: Link<T>,
    tail: Link<T>,
}

struct Node<T> {
    elem: T,
    next: Link<T>,
    prev: Link<T>,
}

impl<T: fmt::Debug> fmt::Debug for List<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut current = self.head.as_ref();
        f.write_str("List [")?;
        let mut first = true;
        
        while let Some(node_rc) = current {
            if !first {
                f.write_str(", ")?;
            }
            first = false;
            
            let node = node_rc.borrow();
            write!(f, "{:?}", node.elem)?;
            current = node.next.as_ref();
        }
        
        f.write_str("]")
    }
}

impl<T> List<T> {
    pub fn push_front(&mut self, elem: T) {
        let new_head = Rc::new(RefCell::new(Node {
            elem,
            next: self.head.take(),
            prev: None,
        }));

        match &mut self.head {
            Some(old_head) => {
                old_head.borrow_mut().prev = Some(new_head.clone());
            }
            None => {
                self.tail = Some(new_head.clone());
            }
        }

        self.head = Some(new_head);
    }
}

关键说明

自动派生的Debug会递归打印每个字段的完整信息:

  • 打印List时会打印head和tail字段
  • 打印Node时会打印elem、next和prev字段
  • 而next和prev又指向其他Node,形成循环递归,最终导致栈溢出。手动实现Debug时只关注元素本身,就能避免这个问题。

内容的提问来源于stack exchange,提问作者RamGorurerChhana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 01:25:28