【问题标题】:Error: reached the recursion limit while instantiating `func::<[closure]>`错误:在实例化 `func::<[closure]>` 时达到递归限制
【发布时间】:2019-07-03 23:13:54
【问题描述】:

我正在尝试测试二叉搜索树是否有效:

use std::{cell::RefCell, rc::Rc};

pub struct TreeNode {
    val: i32,
    left: Option<Rc<RefCell<TreeNode>>>,
    right: Option<Rc<RefCell<TreeNode>>>,
}

pub fn is_valid_bst(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
    preorder_traverse(root.as_ref(), |_| true)
}

fn preorder_traverse<F: Fn(i32) -> bool>(root: Option<&Rc<RefCell<TreeNode>>>, predict: F) -> bool {
    if let Some(node) = root {
        let root_val = root.as_ref().unwrap().borrow().val;
        if !predict(root_val) {
            return false;
        }
        preorder_traverse(node.borrow().left.as_ref(), |v| v < root_val)
            && preorder_traverse(node.borrow().right.as_ref(), |v| v > root_val)
    } else {
        true
    }
}

(Playground):

此代码触发以下错误消息,这对我来说似乎毫无意义:

error: reached the recursion limit while instantiating `preorder_traverse::<[closure@src/lib.rs:19:56: 19:72 root_val:&i32]>`
  --> src/lib.rs:13:1
   |
13 | / fn preorder_traverse<F: Fn(i32) -> bool>(root: Option<&Rc<RefCell<TreeNode>>>, predict: F) -> bool {
14 | |     if let Some(node) = root {
15 | |         let root_val = root.as_ref().unwrap().borrow().val;
16 | |         if !predict(root_val) {
...  |
23 | |     }
24 | | }
   | |_^

我找到了a potentially related Rust issue,但它似乎已经过时了,我无法很好地理解原始问题中引用的消息。

  • 什么达到了递归限制?
  • 如果我想将谓词逻辑封装在闭包或其他东西中,我该如何解决这个问题?

此代码中验证二叉搜索树的算法不正确,但我仍然认为原始代码应该编译

【问题讨论】:

标签: rust closures


【解决方案1】:

@Lukas Kallbertodt 提供了一个更简单的示例,我将以此作为解释的基础:

fn foo<F: Fn()>(x: bool, _: F) {
    if x {
        foo(false, || {}) // line 3
    }
}

fn main() {
    foo(true, || {}); // line 8
}

这里的重点是每个闭包都有一个唯一的类型,所以让我们实例化这个程序:

  • 第一个闭包,在main,我们将类型命名为main#8
  • foo 的第一次实例化,在 mainfoo&lt;[main#8]&gt;
  • 第二次闭包,在foo,我们将类型命名为{foo&lt;[main#8]&gt;}#3
  • foo 的第二次实例化,在foofoo&lt;[{foo&lt;[main#8]&gt;}#3]&gt;
  • 第三次关闭,在foo,我们输入{foo&lt;[{foo&lt;[main#8]&gt;}#3]&gt;}#3
  • foo 的第三次实例化,在 foofoo&lt;[{foo&lt;[{foo&lt;[main#8]&gt;}#3]&gt;}#3]&gt;
  • ...

foo 的每个新实例化创建一个新的闭包类型,每个新的闭包类型创建一个foo 的新实例化,这是没有基本情况的递归:堆栈溢出。 p>


您可以通过在递归调用preorder_traverse 时不创建闭包来解决问题:

  • 使用类型擦除,尽管存在运行时开销,
  • 或者简单地使用单独的内部函数进行递归,因为它独立于F

例子:

fn preorder_traverse_impl(
    root: Option<&Rc<RefCell<TreeNode>>>,
    parent_value: i32,
    predict: fn(i32, i32) -> bool
)
    -> bool
{
    if let Some(node) = root {
        let root_val = root.as_ref().unwrap().borrow().val;
        if !predict(root_val, parent_value) {
            return false;
        }
        preorder_traverse_impl(node.borrow().left.as_ref(), root_val, lessThan)
            && preorder_traverse_impl(node.borrow().right.as_ref(), root_val, greaterThan)
    } else {
        true
    }
}

fn preorder_traverse<F: Fn(i32) -> bool>(root: Option<&Rc<RefCell<TreeNode>>>, predict: F) -> bool {
    if let Some(node) = root {
        let root_val = root.as_ref().unwrap().borrow().val;
        if !predict(root_val) {
            return false;
        }
        preorder_traverse_impl(node.borrow().left.as_ref(), root_val, lessThan)
            && preorder_traverse_impl(node.borrow().right.as_ref(), root_val, greaterThan)
    } else {
        true
    }
}

在 nightly 中,您还可以创建一个谓词类型并为其实现 FnLessThan&lt;i32&gt;GreaterThan&lt;i32&gt;)。

【讨论】:

  • 非常有趣!我完全忘记了闭包可以依赖于外部函数的泛型参数。例如。我们可以像foo(false, || println!("{}", mem::size_of::&lt;F&gt;())) 这样进行递归调用,这很明显这是一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多