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
相关产品推荐
相关产品推荐

