线程 '<main>' 在 Rust 中溢出了它的堆栈

Posted

技术标签:

【中文标题】线程 \'<main>\' 在 Rust 中溢出了它的堆栈【英文标题】:thread '<main>' has overflowed its stack in Rust线程 '<main>' 在 Rust 中溢出了它的堆栈 【发布时间】:2015-05-08 23:00:22 【问题描述】:

我尝试这段代码时出错,它实现了一个简单的链表。

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

struct Node 
    a : Option<Rc<RefCell<Node>>>,
    value: i32


impl Node 
    fn new(value: i32) -> Rc<RefCell<Node>> 
        let node = Node 
            a: None,
            value: value
        ;
        Rc::new(RefCell::new(node))
    


fn main() 
    let first  = Node::new(0);
    let mut t = first.clone();
    for i in 1 .. 10_000
    
        if t.borrow().a.is_none()  
            t.borrow_mut().a = Some(Node::new(i));
        
        if t.borrow().a.is_some() 
            t = t.borrow().a.as_ref().unwrap().clone();
        
    
    println!("Done!");

为什么会这样?这是否意味着 Rust 不如定位安全?

更新: 如果我添加这个方法,程序不会崩溃。

impl Drop for Node 
    fn drop(&mut self) 
        let mut children = mem::replace(&mut self.a, None);

        loop 
            children = match children 
                Some(mut n) => mem::replace(&mut n.borrow_mut().a, None),
                None => break,
            
        
    

但我不确定这是否是正确的解决方案。

【问题讨论】:

确切的错误是什么?在编译时还是运行时? 编译正常。运行程序时出现此错误 "thread '<main>' has overflowed its stack" when constructing a large tree 的可能重复项 这是否意味着 Rust 不如定位安全? - 请在 Rust 的上下文中review what safety means。在这种情况下,“安全”意味着程序不能中止。 【参考方案1】:

这是否意味着 Rust 不如定位安全?

Rust 只对某些类型的故障是安全的;特别是内存损坏崩溃,记录在这里:http://doc.rust-lang.org/reference.html#behavior-considered-undefined

不幸的是,有时人们倾向于认为 rust 对某些类型的非内存破坏的故障更加健壮。具体来说,你应该阅读http://doc.rust-lang.org/reference.html#behavior-considered-undefined。

tldr;在生锈中,许多事情都会引起恐慌。恐慌将导致当前的线程停止,执行关闭操作。

从表面上看,这可能类似于其他语言的内存损坏崩溃,但重要的是要理解,虽然这是应用程序故障,但它不是内存损坏故障。

例如,您可以通过在不同线程中运行操作并在线程恐慌(无论出于何种原因)时优雅地处理失败来处理恐慌之类的异常。

在这个特定示例中,您在堆栈上使用了太多内存。

这个简单的例子也会失败:

fn main() 
  let foo:&mut [i8] = &mut [1i8; 1024 * 1024];

(在大多数 rustc 上;取决于特定实现的堆栈大小)

我原以为使用 Box::new() 将您的分配移动到堆栈会在此示例中修复它...

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

#[derive(Debug)]
struct Node 
    a : Option<Box<Rc<RefCell<Node>>>>,
    value: i32


impl Node 
    fn new(value: i32) -> Box<Rc<RefCell<Node>>> 
        let node = Node 
            a: None,
            value: value
        ;
        Box::new(Rc::new(RefCell::new(node)))
    


fn main() 
    let first  = Node::new(0);
    let mut t = first.clone();
    for i in 1 .. 10000
    
        if t.borrow().a.is_none() 
            t.borrow_mut().a = Some(Node::new(i));
        
        if t.borrow().a.is_some() 
            let c:Box<Rc<RefCell<Node>>>;
             c = t.borrow().a.as_ref().unwrap().clone(); 
            t = c;
            println!(":?", t);
        
    
    println!("Done!");

...但事实并非如此。我真的不明白为什么,但希望其他人可以看看这个并发布一个更权威的答案,说明究竟是什么导致您的代码中的堆栈耗尽。

【讨论】:

“关于究竟是什么导致代码中的堆栈耗尽” - 这应该由重复的问题来解释。 @Shepmaster 个人我仍然不明白另一个问题的答案。如果我有一个 Box 并且它被丢弃,它递归地 对所有子元素执行丢弃,因此不可能丢弃 rust 中的大型图形结构,因为它会在递归调用中溢出吗?这不可能吧? @Shepmaster ...但你似乎是对的。如果你忘记了(t);它工作正常。 这是可能,你只是不能使用朴素的内置递归Drop 实现。这就是为什么链接的问题显示了一个迭代版本,该版本使用固定深度的函数调用删除所有子项。 我实际上对@Doug 的案例更感兴趣,其中有问题的结构是一个被装箱的大型数组。有没有人知道为什么 Box 不能在那里工作?【参考方案2】:

对于那些来到这里并对大型结构是一块连续的内存(而不是一棵盒子树)的情况特别感兴趣的人,我发现了这个 GitHub 问题并进行了进一步讨论,以及一个有效的解决方案为了我: https://github.com/rust-lang/rust/issues/53827

Vec 的方法into_boxed_slice() 返回一个Box&lt;[T]&gt;,并且不会为我溢出堆栈。

vec![-1; 3000000].into_boxed_slice()

注意与 vec! 的区别!文档中的宏和数组表达式:

这将使用 clone 来复制一个表达式,因此对于具有非标准 Clone 实现的类型应该小心使用它。

Vec 上还有with_capacity() 方法,如into_boxed_slice() 示例所示。

【讨论】:

以上是关于线程 '<main>' 在 Rust 中溢出了它的堆栈的主要内容,如果未能解决你的问题,请参考以下文章

线程 'main' 在 Rust 中溢出了它的堆栈

了解 Rust 中的线程安全 RwLock<Arc<T>> 机制

Rust编程语言入门之最后的项目:多线程 Web 服务器

Rust:允许多个线程修改图像(向量的包装器)?

了解Rust中参数化结构的生命周期

为啥 Mutex 被设计为需要 Rust 中的 Arc