使用 `let` 绑定来增加值的生命周期

Posted

技术标签:

【中文标题】使用 `let` 绑定来增加值的生命周期【英文标题】:Using a `let` binding to increase a values lifetime 【发布时间】:2014-11-22 16:20:09 【问题描述】:

我编写了以下代码从stdin 中读取整数数组:

use std::io::self, BufRead;

fn main() 
    let stdin = io::stdin();
    for line in stdin.lock().lines() 
        let xs: Vec<i32> = line.unwrap()
            .trim()
            .split(' ')
            .map(|s| s.parse().unwrap())
            .collect();

        println!(":?", xs);
    

这很好,但是我觉得let xs 行有点长,所以我把它分成了两部分:

use std::io::self, BufRead;

fn main() 
    let stdin = io::stdin();
    for line in stdin.lock().lines() 
        let ss = line.unwrap().trim().split(' ');
        let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();

        println!(":?", xs);
    

这没用! Rust 回复了以下错误:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:6:18
   |
6  |         let ss = line.unwrap().trim().split(' ');
   |                  ^^^^^^^^^^^^^                  - temporary value dropped here while still borrowed
   |                  |
   |                  temporary value does not live long enough
...
10 |     
   |     - temporary value needs to live until here
   |
   = note: consider using a `let` binding to increase its lifetime

这让我很困惑。是line 还是ss 活得不够长?我怎样才能使用let 绑定来增加它们的生命周期?我以为我已经在使用let

我已经阅读了lifetime guide,但我仍然无法弄清楚。谁能给个提示?

【问题讨论】:

【参考方案1】:

这是关于 unwrap() 调用的,它正在获取包含的对象,但此引用应该比容器对象的寿命更长,容器对象超出了下一行的范围(没有本地绑定)。

如果你想得到更干净的代码,一个很常见的写法是:

use std::io::self, BufRead;

fn main() 
    let stdin = io::stdin();
    for line in stdin.lock().lines() 
        let xs: Vec<i32> = line.unwrap()
            .trim()
            .split(' ')
            .map(|s| s.parse().unwrap())
            .collect();

        println!(":?", xs);
    

如果没有,您可以创建与“展开”结果的绑定并使用它。

【讨论】:

所以你是说line 超出了let xs 行的范围,但不在let ss 行中,即使它们在同一个块中? 超出范围的是 line.unwrap() 结果。 谢谢,我想我开始明白了。【参考方案2】:

在您的第二个版本中,ss 的类型是 Split&lt;'a, char&gt;。类型中的生命周期参数告诉我们该对象包含一个引用。为了使赋值有效,引用必须指向该语句之后存在的对象。但是,unwrap() 消耗 line;换句话说,它将Ok 变体的数据移出Result 对象。因此,引用并不指向原来的line,而是指向一个临时对象。

在您的第一个版本中,您在长表达式末尾使用临时变量,尽管调用 map。要修复您的第二个版本,您需要绑定 unwrap() 的结果以保持该值足够长的寿命:

use std::io::self, BufRead;

fn main() 
    let stdin = io::stdin();
    for line in stdin.lock().lines() 
        let line = line.unwrap();
        let ss = line.trim().split(' ');
        let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();

        println!(":?", xs);
    

【讨论】:

为了确保我理解正确,CharSplits 引用了line 中的值。但实际上它引用了来自Ok 的副本,该副本在let ss 行结束后立即被丢弃。而不是仅仅保持所有值直到块结束? 是的。临时值仅对产生该值的表达式所在的语句有效。 注意,这里有两个名为line的变量:第一个是IoResult&lt;String&gt;(=Result&lt;String, IoError&gt;),第二个是Stringunwrap() StringIoResult移动,然后IoResult 将无法使用(这也是我重用名称line 的原因:你不会无论如何都可以使用第一个line)。 String 根本没有被复制。 这很有趣。这样的举动是如何完成的?数据是否被复制而原始数据被销毁?还是堆上的字符串和指向它的原始指针被破坏了? 一个String 结构包含一个Vec,它包含一个指向数据的指针(存储在堆上)、一个长度和一个容量。发生移动时,会复制结构的成员,但不会复制引用的数据,也不运行析构函数。然后编译器使原始副本无法使用(如果您尝试使用该值,则会收到错误消息),因此您无法访问所有权已转移的数据。

以上是关于使用 `let` 绑定来增加值的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

Service的生命周期

fragment生命周期

P04:useEffect 实现 componentWillUnmount 生命周期函数

windchill系统——开发_生命周期状态的增加

vue 生命周期初探

生命周期只执行一次