为什么在 Rust 中不建议递归?

时间:2021-01-29 03:54:19

标签: recursion rust

我熟悉关于递归的一般认识 - 不要使用它,因为它不是一个好的内存管理实践。然而,这个概念应该适用于所有的编程语言,除非它们能很好地处理递归下的内存管理。

在通过 documentation from Educative's Rust course 时,有一个声明:

<块引用>

递归在 Rust 中是可能的,但并不真正被鼓励。相反,Rust 更喜欢迭代,也称为循环。

我无法理解为什么会这样?与其他语言相比,Rust 中是否有一些不那么常见的东西,即不建议使用递归或在 Rust 中处理迭代比其他语言更好

1 个答案:

答案 0 :(得分:9)

<块引用>

我无法理解为什么会这样?与其他语言相比,Rust 中是否存在不建议使用递归或迭代在 Rust 中比其他语言处理得更好的地方?

大多数语言实际上更喜欢迭代而不是递归,他们只是不费心把它拼写得如此明确。例如 Python has an interpreter-level limit on recursion depth,默认值为 1000。

Rust 可能会明确指出这一点,因为它的起源部分在于函数式语言,这些语言基本上是唯一支持递归的语言:许多构造都受到 ML 和 Haskell 的启发,并且最初的编译器是在 OCaml 中,我不认为甚至有一个用于迭代的构造。

至于为什么不喜欢递归,如果没有尾调用消除 (TCE) 递归将导致堆栈空间的消耗,可能是无限的。

如果语言使用 C 堆栈并且除非它也设置了自己的递归限制,否则它无法轻松知道堆栈的深度:Linux 上的默认值可以高达 8MB(实际上是 glibc)和OpenBSD 低至 64k(至少在 openbsd 4 时代)。更有问题的是,堆栈溢出最好会导致段错误(程序硬崩溃),更糟糕的是会导致内存不安全,因此这是您真正想要避免的事情。

但即使没有那个——再次假设你没有 TCE——递归实现的无界循环(例如事件循环)将消耗无限量的内存,因为每个递归调用都会保留一个 {{3}永远不会被回收,因为函数永远不会返回。同时,无限循环可能会消耗 100% 的 CPU(如果您没有做任何等待 IO 事件或锁定的事情),但除非您特别明确地累积数据(例如推送内容),否则不会吃掉所有内存成向量)。

现在,如果您确实拥有 TCE,那么技术上您甚至不需要迭代,而且有些语言可以做到这一点。据我所知,OCaml 或 Erlang 没有任何类型的循环结构,只要您的递归调用处于尾部位置,它就会被优化掉。但当然后一点使它稍微复杂一些,并且容易出错:要使 TCE 工作,调用必须在 尾部位置,这意味着您要做的最后一件事:{ {1}} 是尾调用,但 foo() 不是,addition 在尾位置而不是调用。这意味着您经常需要将您的循环具体化为一个辅助函数,例如使用累加器(例如,在上面的情况下,您将调用 1 + foo() 而不是 1 + foo() 并且它会在内部执行增量,某事像那样)。当然你仍然需要实现和指定 TCE,这需要一些努力,可以限制你的选择等......

至于为什么我在谈论尾调用消除而不是尾调用优化,这是您可能听说过的,优化是启发式的,不能保证。 Rust 有 TCO,因为 stack frame。但这并不能保证优化尾部调用,您可以进行的调用类型受到限制(除了处于尾部位置)并且优化并未在所有后端实施。这意味着仅仅基于你的语言是不够的,如果你想让你的语言在没有迭代结构的情况下工作,你需要在尾调用出现时可靠地删除它们。因此,您需要尾调用消除,而不仅仅是优化。

相关问题