【问题标题】:Lifetimes in lambda-based iterators基于 lambda 的迭代器的生命周期
【发布时间】:2021-08-29 13:32:30
【问题描述】:

我的问题似乎与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() {}

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

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

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

【问题讨论】:

  • 这是一个尝试创建self-referential struct 的示例。 text 被移入闭包中,chunk 也被移入闭包中,指的是text 的内容。结果是一个自引用结构,当前借用检查器不支持。 (虽然自引用结构通常是不安全的,但在 这种情况 中它是安全的,因为 text 是堆分配的,没有变异,并且不会逃脱闭包,所以足够聪明的借用checker 可以证明你正在尝试做的事情是安全的。)
  • 目前尚不清楚这是否会导致错误,尽管很有可能。您引用的讨论的答案说,可以通过选项引用,但之后不能移动结构。在我的例子中,自引用是在 text 和 chunk 被移动到位之后创建的,并且它们永远不会再次移动,所以原则上它应该可以工作。但如果它确实是借用检查器的边缘案例,我可能应该提交一个错误。
  • 它原则上应该可以工作,但众所周知,当前的借用检查器不支持它。 (支持将需要多个新功能:借用检查器应该处理特殊情况的堆分配类型,例如 Box 或 String,其移动不会影响对其 contnet 的引用,在这种情况下 和也证明你没有调整大小或mem::replace()关闭了String。)当然你仍然可以提交错误报告,但你可以确定所有上述限制肯定已经为开发人员所熟悉。跨度>
  • 感谢您的澄清。也许发布您的评论作为答案,以便我可以关闭问题?
  • 我最初没有回答,因为已经有很多关于自引用结构的问题和答案,但是现在关于 this 案例的内容足够多,它可能值得回答,所以我现在发布了一个。

标签: rust


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

    如果您将 chunk Option 移动到闭包中,您的代码就会编译。我不能完全回答 为什么 在闭包之外声明 chunk 会导致在闭包内借用 text 的生命周期错误,但是 chunk 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&lt;Item = Option&lt;String&gt;&gt; 而不是 Iterator&lt;Item&lt;String&gt;&gt;,因为无论如何迭代器永远不会产生 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
            }
        })
    }
    

    注意,如果您将 &amp;str 作为参数并将输出的生命周期与输入参数联系起来,您也可以在不为每个 chunk 分配 String 的情况下使用此迭代器:

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

    【讨论】:

    • 嗨。我将尝试修改我的示例,因为我认为我没有很好地表达我的目标。我需要在外部声明块,因为块需要保持之前调用next 的状态。 (虽然现在我想到了,我可以只保留分隔块开始和位置的整数。)在这个例子中,额外的选项和移动而不是引用是不清楚的,但我需要那些用于这个例子的原始代码被简化了。
    • 是的,只需关闭块的开头和结尾,并在本地构建切片即可。不过,我会留下我的问题。应该可以以某种方式关闭切片...
    猜你喜欢
    • 2020-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多