【问题标题】:Stack overflow while evaluating trait requirement评估特征要求时堆栈溢出
【发布时间】:2020-07-07 20:36:27
【问题描述】:

我有一个函数可以对给定Items 上的任何迭代器进行可变引用。该函数通常可以一个接一个地使用项目,但有时必须执行前瞻。像这样检索到的项目有时会被消耗,但有时必须“添加”回迭代器(例如使用 Chain),然后此函数必须在迭代器上递归。

但是,在解决 trait 要求时,执行会在运行时崩溃:

error[E0275]: overflow evaluating the requirement `std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, &mut std::iter::Chain<std::vec::IntoIter<std::string::String>, ...

最小代码是(这里的条件表示无法达到无限递归深度):

fn foo<I: Iterator<Item = String>>(it: &mut I) -> String {
    if *(&1) == 1 {
        String::new()
    } else {
        foo(&mut vec![String::new()].into_iter().chain(it))
    }
}

fn main() {
    let mut it = vec!["Hello".to_string(), "World!".to_string()].into_iter();
    println!["{:?}", foo(&mut it)];
}

Playground

将函数更改为接受 trait 对象可以解决问题,但我并不热衷于在这种简单情况下使用动态调度。

我是否必须重构代码、使用 trait 对象,或者是否有其他解决方案可以阻止检查器无限期地递归?

我在 x86_64-apple-darwin 上使用 Rust 1.44.1,但它也在 nightly 下崩溃。

【问题讨论】:

标签: recursion rust iterator traits


【解决方案1】:

您的错误情况可能会简化为:

fn f(i: impl Iterator<Item = ()>) {
    f(std::iter::once(()).chain(i)) // resolves to f(Chain<Once, I>)
}

fn main() {
    f(vec![].into_iter())
}

导致错误:

overflow evaluating the requirement `std::iter::Chain<std::iter::Once<()>, std::iter::Chain<std::iter::Once<()>, ..>>

发生这种情况是因为静态调度必须在编译时实例化。编译器可能会假设深度为 10,但如果它达到深度 11,应该执行什么?您已经提到的直接解决方案是通过 trait 对象进行动态调度,因为您的情况如下所示:

fn foo<I: Iterator<Item = String>>(it: &mut I) -> String {
    if *(&1) == 1 {
        String::new()
    } else {
        let mut it: Box<dyn Iterator<Item = _>> =
            Box::new(vec![String::new()].into_iter().chain(it));
        foo(&mut it) // might also case in-place with `as Box<dyn Iterator...>`
    }
}

另一种方法是用迭代替换递归(这也可能减少编译时间),正如@loganfsmyth 指出的那样,peekable 可能很适合您。此外,seq 也可能对这种情况有所帮助。

【讨论】:

  • 谢谢。 Peekable 是我寻求的解决方案,因为我必须让更深层次的递归帧来改变迭代器。
【解决方案2】:

正如@Kitsu 的回答所解释的,您当前的代码存在问题,因为编译器无法确定递归的深度。

如果您的基本要求是您的功能是:

  • 从迭代器中获取一个或多个值
  • 取决于一些逻辑,或者:
    • 返回结果,或
    • 将值放回迭代器,然后递归到自身

那么这可能是一个解决方案:

fn foo<I: Clone + Iterator<Item = String>>(mut it: I, n: i32) -> String {
    let mut start_iter = it.clone();
    let s = it.next();
    if n>4 {
        "Done".to_string()
    } else {
        foo(start_iter, n+1)
    }
}

fn main() {
    let mut it = ["apples", "bananas", "oranges", "mandarins", "peaches", "pears"].iter()
        .map(|s| s.to_string()).collect::<Vec<String>>().into_iter();
    println!["{:?}", foo(it, 0)];
}

我假设您必须有一些其他状态允许函数确定何时停止递归 - 在我设计的示例中,我刚刚传递了一个额外的 i32 参数。

请注意,迭代器的克隆成本通常很低(可能比构建链式迭代器便宜很多,尤其是盒装的迭代器)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-28
    • 1970-01-01
    • 1970-01-01
    • 2018-11-12
    • 2019-05-18
    • 2019-10-13
    • 2021-08-12
    • 2016-11-09
    相关资源
    最近更新 更多