关于 RUST 生命周期的疑问

2022-05-12 10:36:53 +08:00
 liangzimo

这是代码

fn main() {
    let n = String::from("rust");
    let myname;
    {
        let obj = AA{
            name: &n
        };
        myname = obj.get_name();
    }
    println!("{}", myname)

}

struct AA<'a>{
    name: &'a str
}

impl <'a> AA<'a>{
    fn get_name(&self) -> & str{
        return &self.name;
    }
}

按照文档来说,下面的报错能理解,因为 myname 的生命周期应该和 obj 是一样的,出了作用范围就被编译器检查出来了

error[E0597]: `obj` does not live long enough
  --> src/main.rs:14:18
   |
14 |         myname = obj.get_name();
   |                  ^^^^^^^^^^^^^^ borrowed value does not live long enough
15 |     }
   |     - `obj` dropped here while still borrowed
16 |     println!("{}", myname)
   |                    ------ borrow later used here

但是,如果我把方法返回值写明返回的生命周期,如下

impl <'a> AA<'a>{
    fn get_name(&self) -> &'a str{
        return &self.name;
    }
}

这样能正常运行

warning: `hello_cargo` (bin "hello_cargo") generated 4 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
     Running `target/debug/hello_cargo`
rust

疑问:为什么会产生这样的区别,按照生命周期省略规则的第三条规则来说默认的返回生命周期是&self 的,也就是 obj 的,这个生命周期和'a 是不一样的吗

2587 次点击
所在节点    Rust
14 条回复
czzhengkw
2022-05-12 10:43:55 +08:00
rust 新手,说一下自己的看法

因为声明了 `get_name()` 返回的引用生命周期是 `name` 的生命周期,而不是 `obj` 的,而 `obj` 的构建是使用 `n` 的引用。编译器可以识别出 `n` 的生命周期能满足要求,所以不会报错
liangzimo
2022-05-12 10:50:35 +08:00
@czzhengkw 感谢,同新手,显示添加的'a 的代表 n 的生命周期吗,这个就很不好理解
bearice
2022-05-12 10:53:57 +08:00
fn get_name(&self) -> & str 返回的 lifetime 是 self 的, 而 self (就是 obj 这个变量)在 15 行就没了
变量的生命周期和变量所代表的类型的生命周期是两回事儿
bearice
2022-05-12 10:56:57 +08:00
fn get_name(&self) -> & str
写成这样也许更容易理解
fn get_name(self : &'b AA<'a> ) -> &'b str
liangzimo
2022-05-12 11:00:22 +08:00
@bearice 那显示添加的'a 是 n 的生命周期吗,因为只有这样编译器才会通过检查吧,这两者是怎么关联上的
saltbo
2022-05-12 11:01:15 +08:00
@bearice 意思是 self 的生命周期并不是伴随整个 struct 的吗 出了 method 的作用于就没了?
bearice
2022-05-12 11:10:49 +08:00
@liangzimo 不是 n 的,而是指示 n 所引用的对象的。
就相当于你告诉编译器,保证类型 AA 中的 n 所引用的值在'a 这个生命周期内都有效。
这个周期和 类型为 AA 的变量的生命周期 是两回事儿。
Kilerd
2022-05-12 11:25:06 +08:00
@bearice 说的没错,具体分析一下:
```
1 fn main() {
2 let n = String::from("rust");
3 let myname;
4 {
5 let obj = AA{
6 name: &n
7 };
8 myname = obj.get_name();
9 }
10 println!("{}", myname)
11
12 }
```

n 的生命周期是 2-12
myname 是从 3-12
obj 的是 5-9



fn get_name(&self) -> & str 这个代码为什么当前版本能运行,是因为 Rust 为了简化生命周期的繁琐写法,给出三条自动补全规则,这里命中其中一条「如果 parameter 和 return value 有且仅有一个 ref ,便给他自动补上一个匿名的 lifetime 」 也就是。fn get_name(&'_ self) -> &'_ str 。 值得注意的是这里的 _ 并不是你在结构体上写的 'a ,所以在经历了某个阶段之后会变成 @bearice 说的 fn get_name(&'ccc self) -> &'ccc str 。而这个 ccc 是来自于 struct ref 的,所以生命周期只能是遵循 obj 的生命周期,所以是 5-9


当你写成 fn get_name(& self) -> &'a str 的时候发生了什么呢? 注意的是 'a 是来自于 field name 的,name 的值 &n 的生命周期是 n 的生命周期,即 2-12 , 也就是说 小生命周期(5-9)的 obj 接纳了一个长生命(2-12)的 &n ,这显然是允许的

fn get_name(& self) -> &'a str 这种写法就是明确的告诉愚蠢的 rustc ,我返回的值是 ‘a 那么长的,别瞎搞我。 然后 rustc 你看 返回值 'a >= &n 的 ‘a ,ok ,pass , 这就返回了一个 ’a 的 &&n
Buges
2022-05-12 11:27:44 +08:00
记住生命周期是泛型参数的一种。 &'a T 是 Ref<A,T>的语法糖。
gydi
2022-05-12 11:29:43 +08:00
好像可以这样理解
struct AA<'a> 这个生命周期不是定义 AA 实例的,而是定义具体用到该生命周期的属性的。
和#4 提到的一样
fn get_name(&self) -> &str 等价于 fn get_name(&'b self) -> &'b str
而 fn get_name(&self) -> &'a str 等价于 fn get_name<'t>(&'t self) -> &'a str where 'a : 't
这时'a 就可以由 AA 声明时传入的 name 的生命周期去推导出来,再检查
liangzimo
2022-05-12 11:37:58 +08:00
@bearice @Buges @Kilerd 感谢几位,理解了!
@gydi 这个想法好,一下就通了
libook
2022-05-12 12:09:14 +08:00
我也是初学者,尝试分析一下这个问题。

生命周期第三条规则是默认应用的,你自己标注生命周期,就会覆盖这个默认声明,也就是说第三条规则不再自动应用,编译器会按照你标注的生命周期来检查生命周期。

修改后,get_name 的输出生命周期与 AA 实例一样,AA 的输入生命周期与 n 一样,那么只要 n 还活着,你就可以用 get_name 的返回值。
libook
2022-05-12 12:27:03 +08:00
@bearice #7 请问我如此理解是否正确:
方法默认的输入生命周期实际上有两个,一个是&self ,另一个是方法所对应的类型(即本题目中的结构体),生命周期省略规则的第三条只是说方法输出值的生命周期默认和&self 一样,但也默认和方法所对应的类型不同。
bearice
2022-05-12 13:53:55 +08:00
@libook 这么说也没问题

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/852344

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX