关于 rust 的生命周期的 困惑

qq_33456101 2016-10-05 12:45:53
学了很久,想搞清楚Rust的生命周期到底是干什么用的,可一直搞不明白,网上讲解 生命周期的文章很多,看完之后,我就更困惑了,我很难相信写这些文章的作者是真的理解了,不知道有没有高手,可以真正把这个问题说明白的。
帖一段代码来说明我的困惑:

fn s1<'a>(x: &'a i32) -> &'a i32 {
return &x;
}
fn s2<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
print!("{}", y);
return &x;
}
fn s3<'a, 'b: 'a>(x: &'a i32, y: &'b i32) -> &'a i32 {
print!("{}", x);
return &y;
}

fn main() {
let x = 1;
{
let y = 2;
let r1 = s1(&x);
print!("{}", r1);

let r2 = s2(&x, &y);
print!("{}", r2);

let r3 = s3(&x, &y);
print!("{}", r3);

let r4 = s3(&y, &x);
print!("{}", r4);
}

}


上面这个例子是可以正常工作的, 但它却让我彻底糊涂了,里面存在几个令人疑惑的问题:

1. 函数s2 中的'a 究竟是什么? 它要求x和y有相同的生存域,显然不是,因为例子中的x和y的生存域明显不同。那她到底代表什么含义?
如果把所有的'a 全部去除, 编译器也完全能够判断出返回值的生存域是否合法, 但是,rust却强制在每个参数位置写一次'a, 这个完全没有意义的行为到底是有什么用意呢?

2. 函数s3 的逻辑似乎清晰一点,从声明上看'a是x 的生存域, 'b是y 的生存域, 并且x的生存域在y的生存域之内, 返回值与x的生存域相同。
函数声明中,自以为是的定义了 'a 包含在'b 之内,然后让代码编译通过了,可实际的代码调用,却完全不是这么回事, s3(&x, &y)和s3(&y, &x)都是允许的,于是, “ 'a是x 的生存域, 'b是y 的生存域” 这个理解显然就是错误的
更让人难以理解的是'a 和'b 的包含关系完全是自己随意编造的, 你总是有办法编造一个方案让编译通过,那这样做编译器的所谓检查到底有什么价值?
按理来说只要指定好返回值的生存周期,rust不就自己能够来判断生存域是否合法了么,干嘛要搞出这么复杂的语法,让程序员写一遍呢?想必是有 什么原因的吧,但网上的教程实在是太简单了,没有人深度解释一下里面的机理,不知是否有高手可以代为解释一下。
...全文
558 5 打赏 收藏 转发到动态 举报
写回复
用AI写文章
5 条回复
切换为时间正序
请发表友善的回复…
发表回复
wuxingren888 2019-10-03
  • 打赏
  • 举报
回复
引用 4 楼 laurencechan的回复:
楼主的问题我也是我最近在困惑的问题,不知道楼主理解了没有?
我也是看了很多的文章大概明白了这个意思,不知道我理解的对不对。 先看这个函数: fn foo(x: &str, y: &str) -> &str { if true { x } else { y } } 函数本身没有问题,返回值是x。但是不能通过编译,因为编译器要推导这个函数引用有没有问题,从返回值开始检查 再检查到输入参数上,看返回值的生命周期是不是比输入参数短?假如返回值生命周期包含(等于小于)在输入参数生命周期内,程序引用是正确的,负责错误。 这就产生了第一个问题:编译器并不运行程序,所以不知道应该返回哪一个,所以无法进行上下文比较,直接拒绝编译。 这时候得你告诉编译器,返回值的生命周期多长?参数生命周期多长? 所以有了下面的例子: fn foo<'a>(x: &'a str, y: &'a str) -> &'a str { if true { x } else { y } } a'在这里没有实际的含义,它只是一个标记 ,为了表示一种关系。我们把a'就当作2个小时(00:00-02:00)吧! 现在你告诉编译器不管返回哪个,返回值的生命周期是a' 2个小时,然后编译器发现两个输入参数都是a' 2小时,返回值生命周期包含在(小于等于)输入参数生命周期内,规则正确可以编译。 假如写成这样: fn foo<'a, 'b> (x: &'a str, y: &'b str) -> &'a str { if true { x } else { y } } 错误! 返回值生命周期是a',如果返回x程序还算正确,但是如果返回y,输入参数y的生命周期是b',编译器并不知道a'和b'的包含关系(谁大谁小),所以还是拒绝编译。 假如我们把b'当作3个小时(00:00-03:00),所以a'包含于(小于)b'。把这种关系提前告诉编译器: fn foo<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str { if true { x } else { y } } 正确! 编译器开始比较返回值无论返回哪个,'a都包含于输入参数生命周期'a ,'b中。 'a写成a'了,不想改,凑合看吧!望见谅。我也是业余初学者 不对的地方请指出。至于为什么输出参数生命周期要包含于输入参数生命周期,我就不解释了。
laurencechan 2019-09-17
  • 打赏
  • 举报
回复
楼主的问题我也是我最近在困惑的问题,不知道楼主理解了没有?
chenxiqi 2016-10-16
  • 打赏
  • 举报
回复
回复的内容都没错 ,可惜答非所问
gongxufan 2016-10-13
  • 打赏
  • 举报
回复
最后说一下。不要把lifetime标记和参数类型等同啊,标记只是一种约定不会进行静态在入参处检验,编译器会在上下文推导。
gongxufan 2016-10-13
  • 打赏
  • 举报
回复
我只说一点,生命周期标记不是严格的类型匹配。也就是说编译器只按照下面的规则推导,而不会像函数参数类型那样在入参就检查x和y的scope包含关系。

规则如下:函数的输出值,记为Out,函数输出值依赖的输入值集合记为In。合法的生命周期需要满足,Out的生命周期小于等于In。注意这里In在依赖输入参数为多个的情况下,是多个入参生命周期的交集。

回到s3这个方法,输出值只依赖输入值y,而且根据上下文输出值的生命期跟y在一个作用域,x和y交换调用,那么输出值比x要小。所以运行没问题。这里生命周期跟输入参数x没一毛钱关系。


废那么多话,你要明白一点,lifetime是干嘛。就是为了防止函数返回一个引用或者指针,指向函数里面的生命周期比这个指针变量要短。也就是野指针。所以关键是函数输出值依赖哪些变量,那么他的生命周期必须小于等于这些依赖项生命期的交集。细心想想野指针你就会明白。自己领悟去吧。

3,423

社区成员

发帖
与我相关
我的任务
社区描述
其他开发语言 其他开发语言
社区管理员
  • 其他开发语言社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧