我是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
但说实话,我不知道编译器试图告诉我什么。 我所理解的是,我有一个终身问题。但为什么?
两个代码示例之间有什么区别?为什么以及如何影响生命周期?
答案 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()
返回的值取得锁的所有权,因此承担责任,只要它需要,然后摧毁它就能使它保持活着。