使用 `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<'a, char>
。类型中的生命周期参数告诉我们该对象包含一个引用。为了使赋值有效,引用必须指向该语句之后存在的对象。但是,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<String>
(=Result<String, IoError>
),第二个是String
。 unwrap()
将String
从IoResult
中移动,然后IoResult
将无法使用(这也是我重用名称line
的原因:你不会无论如何都可以使用第一个line
)。 String
根本没有被复制。
这很有趣。这样的举动是如何完成的?数据是否被复制而原始数据被销毁?还是堆上的字符串和指向它的原始指针被破坏了?
一个String
结构包含一个Vec
,它包含一个指向数据的指针(存储在堆上)、一个长度和一个容量。发生移动时,会复制结构的成员,但不会复制引用的数据,也不运行析构函数。然后编译器使原始副本无法使用(如果您尝试使用该值,则会收到错误消息),因此您无法访问所有权已转移的数据。以上是关于使用 `let` 绑定来增加值的生命周期的主要内容,如果未能解决你的问题,请参考以下文章