链接函数调用vs使用中间变量

时间:2014-12-14 10:50:05

标签: compiler-errors rust lifetime

我是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

但说实话,我不知道编译器试图告诉我什么。 我所理解的是,我有一个终身问题。但为什么?

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

3 个答案:

答案 0 :(得分:6)

定义中间变量可延长中间值的生命周期。临时值(例如io::stdin()中的io::stdin().lock()io::stdin().lock().lines())不再存在于语句的末尾,除非它们被移动(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()的结果。

答案 1 :(得分:1)

                                             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不允许这个

答案 2 :(得分:1)

我只是想我会重温这个问题,因为现在有些细节不同了。 (说实话,我自己也不明白这一点,所以我决定深入研究这些内容并记录我的发现。)

我们从这段代码开始:

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<'a>。此类型的'a参数表示StdinLock内的某个引用必须至少'a有效。另一方面,从lock是我们函数的局部变量的事实我们知道它的范围是这个函数的主体,并且从它的类型是StdinLock<'a>的事实来看,编译器得出结论'a是与函数体相对应的范围。

正是在这一点上,当我们意识到对.lock()的调用有效时,传递给它的 self参数必须为整个函数体提供活动,因为类型告诉我们.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()返回的值取得锁的所有权,因此承担责任,只要它需要,然后摧毁它就能使它保持活着。