基于 lambda 的迭代器的生命周期

问题描述

我的问题似乎与Rust error "cannot infer an appropriate lifetime for borrow expression" when attempting to mutate state inside a closure returning an Iterator密切相关,但我认为不一样。所以,这个

use std::iter;                                                                  

fn example(text: String) -> impl Iterator<Item = Option<String>> {            
    let mut i = 0;   
    let mut chunk = None;   
    iter::from_fn(move || {   
        if i <= text.len() {   
            let p_chunk = chunk;
            chunk = Some(&text[..i]);   
            i += 1;   
            Some(p_chunk.map(|s| String::from(s))) 
        } else {   
            None   
        }   
    })   
}   

fn main() {}

不编译。编译器说它无法确定 &text[..i] 的适当生命周期。这是我能想到的最小的例子。这个想法是,有一个内部状态,它是 text一个切片,迭代器返回从该内部状态分配的新字符串。我是 Rust 的新手,所以也许这很明显,但是我将如何注释生命周期以便编译?

请注意,此示例与链接示例不同,因为将 point 作为引用传递,而此处的 text 已移动。另外,这个答案现在已经有一年半的时间了,所以也许有更简单的方法

编辑: 添加 p_chunk 以强调 chunk 需要在对 next调用之间保持持久性,因此不能是闭包的本地,但应该被捕获通过它。

解决方法

如果您将 chunk Option 移动到闭包中,您的代码将被编译。我不能完全回答为什么在闭包外声明 chunk 会导致闭包内借用 text 的生命周期错误,但是 chunk {{ 1}} 无论如何看起来都是多余的,以下代码应该是等效的:

Option

此外,您似乎不太可能真正需要 fn example(text: String) -> impl Iterator<Item = Option<String>> { let mut i = 0; iter::from_fn(move || { if i <= text.len() { let chunk = text[..i].to_string(); i += 1; Some(Some(chunk)) } else { None } }) } 而不是 Iterator<Item = Option<String>>,因为迭代器无论如何都不会产生 Iterator<Item<String>>

Some(None)

注意,如果您将 fn example(text: String) -> impl Iterator<Item = String> { let mut i = 0; iter::from_fn(move || { if i <= text.len() { let chunk = text[..i].to_string(); i += 1; Some(chunk) } else { None } }) } 作为参数并将输出的生命周期与输入联系起来,您也可以使用此迭代器而不为每个 String 分配 chunk论点:

&str
,

您的代码是尝试创建 self-referential struct 的示例,其中结构由闭包隐式创建。由于 textchunk 都被移到了闭包中,因此您可以将它们视为结构体的成员。由于 chunk 引用了 text 中的内容,因此结果是一个自引用结构,当前借用检查器不支持该结构。

虽然自引用结构由于移动而通常是不安全的,但在这种情况下它是安全的,因为 text 是堆分配的并且随后不会发生变异,也不会逃脱闭包。因此,text 的内容不可能移动,一个足够聪明的借用检查器可以证明您正在尝试做的事情是安全的,并允许闭包编译。

[链接问题] 的答案表示可以通过 Option 进行引用,但之后无法移动结构。在我的例子中,自引用是在文本和块移动到位后创建的,并且它们永远不会再次移动,所以原则上它应该可以工作。

同意 - 原则上它应该可以工作,但众所周知,当前的借用检查器不支持它。支持需要多个新功能:借用检查器应该特殊情况的堆分配类型,如 BoxString,其移动不会影响对其内容的引用,并且在这种情况下还证明您不要调整大小或mem::replace()封闭的String

在这种情况下,最好的解决方法是“显而易见”的解决方法:不是持久化 chunk 切片,而是持久化一对 usize 索引(或 Range)并创建切片当你需要的时候。