【问题标题】:Chaining function calls vs using intermediate variables链接函数调用与使用中间变量
【发布时间】:2014-12-14 10:50:05
【问题描述】:

我是 Rust 新手,我发现很难理解整个所有权/借用概念。 ...即使在阅读了所有官方指南之后。

为什么下面的代码编译没有任何问题?

use std::io;

fn main() {
    let mut input = io::stdin(); 
    let mut lock = input.lock(); 
    let mut lines_iter = lock.lines();

    for line in lines_iter {
        let ok = line.ok();
        let unwrap = ok.unwrap();
        let slice = unwrap.as_slice();

        println!("{}", slice);
    }
}

...但这不是?

use std::io;

fn main() {
    let mut lines_iter = io::stdin().lock().lines();

    for line in lines_iter {
        let slice = line.ok().unwrap().as_slice();
        println!("{}", slice);
    }
}

从我幼稚的角度来看,两个代码示例的作用完全相同。唯一的区别是第一个使用一些中间变量,而第二个是链接函数调用。

在编译第二个时,它对我大喊大叫

 - error: borrowed value does not live long enough
 - note: reference must be valid for the block at 
 - note:...but borrowed value is only valid for the statement  
 - help: consider using a `let` binding to increase its lifetime

但老实说,我不知道编译器想告诉我什么。 我所了解的是,我有一生的问题。但为什么?

这两个代码示例有什么区别?为什么以及如何影响什么的生命周期?

【问题讨论】:

    标签: compiler-errors rust lifetime


    【解决方案1】:

    定义中间变量可以延长中间值的生命周期。临时值(例如io::stdin().lock().lines() 中的io::stdin()io::stdin().lock())在语句末尾不再存在,除非它们被移动(io::stdin().lock() 就是这种情况)。

    let mut lines_iter = io::stdin().lock().lines();:

    • io::stdin() 返回一个新的 Stdin
    • .lock() 返回一个新的StdinLock<'a>(它引用了Stdin;您在文档中看不到<'a>,因为生命周期在源代码中是elided
    • .lines() 返回一个新的Lines<StdinLock<'a>>(获取锁的所有权)。

    .lock()返回类型的lifetime参数表示锁是从Stdin对象借用的。当您从某个对象借用时,该对象必须至少与借用一样长。但是,您试图让一个变量持续到函数结束,但该变量从将在语句末尾删除的对象中借用(因为 io::stdin() 是一个临时值)。

    历史记录:最初提出这个问题时,.lines() 会从锁中借用。现在,.lines() 获得了锁的所有权。这意味着现在,只需要将io::stdin() 绑定到一个变量;不再需要绑定input.lock()的结果。

    【讨论】:

    • 好的,我只会接受“具有生命周期参数的中间值在语句末尾不再存在”,......看来我必须阅读更多关于生命周期的内容。但是:“使用中间值”真的是解决这个问题的方法吗? ...这看起来更像是一种多余的解决方法。
    • 现在,也许将来推理算法可以扩展到生命周期,但是不这样做的主要原因是功能性:显式优于隐式,尤其是在持有 lock.
    • 来自主要的“高级”语言,如 java、scala、haskell 或 dart 等......这种行为在我看来非常“令人不安”。我无法想象以这种方式开发更复杂的代码有多烦人。我希望他们尽快解决这个问题。谢谢大家的帮助!
    【解决方案2】:
                                                 XXXXXXXXXXX
                                          XXXXXXXX          XXXXXXX
                                         XX Gets destroyed after  X
                                         X  end of statement     XX
                                         XX if not binded      XX
                                 +-----+  XXXXXX      XXXXXXXXXX
                                 |             XXXXXXXX
                                 v
    
    +-------------+       +-------------+        +----------------+
    |  .lock()    |       |  io::stdin()|        |                |
    |             +-------->            +-------->   Global       |
    |   Lock      |       |StdinReader           |Stdin Buffer    |
    |             |       |             |        |                |
    |             |       |             |        |                |
    +------^------+       +-------------+        +----------------+
           |
           |
           |
           |
    +------+-------+
    |   .lines()   |
    |              |
    |  Iterator    |
    |              |
    |              |
    +--------------+
    

    所以 Rust 不允许这样做

    【讨论】:

      【解决方案3】:

      我只是想重新审视这个问题,因为现在有些细节有所不同。 (说实话,我自己就是不明白,所以我决定深入研究这些东西并记录我的发现。)

      我们从这段代码开始:

      use std::io::{stdin, BufRead};
      
      fn main() {
          for l in stdin().lock().lines() {
              println!("{}", l.unwrap());
          }
      }
      

      这是编译器必须说的:

      t.rs:4:14: 4:21 error: borrowed value does not live long enough
      t.rs:4     for l in stdin().lock().lines() {
                          ^~~~~~~
      t.rs:4:5: 6:6 note: reference must be valid for the destruction scope surrounding statement at 4:4...
      t.rs:4     for l in stdin().lock().lines() {
      t.rs:5         println!("{}", l.unwrap());
      t.rs:6     }
      t.rs:4:5: 6:6 note: ...but borrowed value is only valid for the statement at 4:4
      t.rs:4     for l in stdin().lock().lines() {
      t.rs:5         println!("{}", l.unwrap());
      t.rs:6     }
      t.rs:4:5: 6:6 help: consider using a `let` binding to increase its lifetime
      

      让我们尝试一些更简单的方法:

      fn main() {
          let lock = stdin().lock();
      }
      

      这仍然不起作用,错误非常相似。这不起作用的事实告诉我们问题出在stdin() 调用上。

      t.rs:4:16: 4:23 error: borrowed value does not live long enough
      t.rs:4     let lock = stdin().lock();
                            ^~~~~~~
      t.rs:3:11: 5:2 note: reference must be valid for the block at 3:10...
      t.rs:3 fn main() {
      t.rs:4     let lock = stdin().lock();
      t.rs:5 }
      t.rs:4:5: 4:31 note: ...but borrowed value is only valid for the statement at 4:4
      t.rs:4     let lock = stdin().lock();
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~
      t.rs:4:5: 4:31 help: consider using a `let` binding to increase its lifetime
      

      让我们看看类型。 stdin 返回Stdin,它有.lock 方法:

      fn lock(&self) -> StdinLock
      

      方法的返回类型是StdinLock,但是如果你看它的声明,你会发现它使用了一个生命周期参数:

      pub struct StdinLock<'a> {
          // some fields omitted
      }
      

      省略的字段是这个参数的原因,通过查阅sources我们得知里面有一个MutexGuard,并且生命周期限制是应用到守卫里面存储的值的类型。但这实际上根本不重要。重点是这个生命周期参数,也就是说有lifetime elision参与,lock方法的声明其实是这样的:

      fn lock<'a>(&'a self) -> StdinLock<'a>  /* Self = Stdin */
      

      所以。我们本地的lock 变量的类型为StdinLock&lt;'a&gt;。这个类型的'a 参数意味着StdinLock 内部有一些引用必须至少对'a 有效。另一方面,从 lock 是我们函数的局部变量这一事实我们知道它的范围是这个函数的主体,并且从它的类型是 StdinLock&lt;'a&gt; 的事实,编译器得出结论 'a 是函数体对应的范围。

      此时我们意识到要使对.lock() 的调用有效,传递给它的self 参数必须在整个函数体中都处于活动状态,因为types 告诉我们.lock() 返回的值保留了对它的某些部分的引用。但它会在语句结束时立即被销毁,除非我们明确使用let 来帮助它延长寿命。

      我们最终拥有:

      use std::io::{stdin, BufRead};
      
      fn main() {
          let stdin = stdin();
          for l in stdin.lock().lines() {
              println!("{}", l.unwrap());
          }
      }
      

      这恰好起作用。

      就是这样,一如既往,一切都归结为所有权问题。从.lock() 返回的值不拥有它被调用的内容(在我们的例子中是stdin() 的结果),但它保留了对它的引用。这意味着必须有人负责保持stdin() 调用的结果并在某个时候销毁它,并且必须有人是你(和我),因为没有其他选择;)。

      另一方面,.lines() 的类型是这样的:

      fn lines(self) -> Lines<Self>  /* Self = StdinLock */
      

      如你所见,它消耗了self,所以我们不必显式绑定.lock()的结果,因为.lines()返回的值拥有锁的所有权,因此承担了保留它的责任只要它需要就活着,然后摧毁它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-06-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-20
        相关资源
        最近更新 更多