调试打印语句导致具有std::rc::Rc字段类型的结构体堆栈溢出。

5

我正在按照这本中的链表实现进行操作。 ListNode 结构如下 -

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

#[derive(Debug)]
pub struct List<T> {
    head: Link<T>,
    tail: Link<T>,
}

#[derive(Debug)]
struct Node<T> {
    elem: T,
    next: Link<T>,
    prev: Link<T>,
}

我有一个类似下面的push_front方法 -
    pub fn push_front(&mut self, elem: T) {
        let new_node = Node::new(elem);
        let new_node = Rc::new(RefCell::new(new_node));
        let mut old_head = self.head.take();
        old_head.as_mut().map(|node| {
            node.borrow_mut().prev = Some(Rc::clone(&new_node));
        });
        new_node.borrow_mut().next = old_head.clone();
        self.head = Some(new_node);
        if self.tail.is_none() {
            self.tail = self.head.clone();
        }
    }

代码可以正常编译。但是,每当我在测试中加入一个调试打印代码List ,就会出现一个测试失败,并显示消息 thread 'tests::test_list_1' has overflowed its stack
    #[test]
    fn test_list_1() {
        let mut list = List::new();
        list.push_front(1);
        list.push_front(2);
        list.push_front(3);
        println!("{:?}", list);
        // assert!(false);
    }

如果我将println!这行注释掉,那么测试就能通过。所以基本上是list的调试输出导致了栈溢出问题。我无法理解究竟是什么原因导致了这个问题。
完整代码的Playground链接
我已经对代码进行了一些实验,找到了导致问题的具体代码行。
println!("{:?}", list);

因为list具有类型std::rc::Rc,而println!宏获取值的引用,所以我猜想它在某个地方创建了一个循环。虽然我无法具体识别问题。


1
我的猜测是,您在打印列表时尝试对所有指针进行解引用。请尝试实现Debug特质,以仅在一个方向上跟踪。 - Simson
1个回答

6
由于派生的 impl Debug for Node 会打印出 prevnext,但是 prev 会试图打印它的下一个节点(即当前节点),而 next 会试图打印它的 prev 节点(同上),从而导致了无限递归。
您需要手动实现 Debug
impl<T: Debug> std::fmt::Debug for Node<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Node")
            .field("elem", &self.elem)
            .field("next", &self.next)
            .finish()
    }
}

如果不想打印tail,则可以手动为List实现Debug


我同意这个解决方案是可行的,但如果您想打印prevOption以便知道它是Some还是None,该怎么修改这个解决方案呢?因此,如何避免无限递归,但仍然能够完全分析它以进行调试?也许可以使用.field("prev", if is_some(&self.prev) { "Some" } else { "None"})或类似的方法。 - Kevin Anderson
1
@KevinAnderson 你需要使用引用 (if is_some(&self.prev) { &"Some" } else { &"None"})。也可以像这样 .field("has_prev", &self.prev.is_some()) - Chayim Friedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接