V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liangzimo
V2EX  ›  Rust

关于 RUST 生命周期的疑问

  •  
  •   liangzimo · 2022-05-12 10:36:53 +08:00 · 2294 次点击
    这是一个创建于 686 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是代码

    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 是不一样的吗

    14 条回复    2022-05-12 13:53:55 +08:00
    czzhengkw
        1
    czzhengkw  
       2022-05-12 10:43:55 +08:00
    rust 新手,说一下自己的看法

    因为声明了 `get_name()` 返回的引用生命周期是 `name` 的生命周期,而不是 `obj` 的,而 `obj` 的构建是使用 `n` 的引用。编译器可以识别出 `n` 的生命周期能满足要求,所以不会报错
    liangzimo
        2
    liangzimo  
    OP
       2022-05-12 10:50:35 +08:00
    @czzhengkw 感谢,同新手,显示添加的'a 的代表 n 的生命周期吗,这个就很不好理解
    bearice
        3
    bearice  
       2022-05-12 10:53:57 +08:00
    fn get_name(&self) -> & str 返回的 lifetime 是 self 的, 而 self (就是 obj 这个变量)在 15 行就没了
    变量的生命周期和变量所代表的类型的生命周期是两回事儿
    bearice
        4
    bearice  
       2022-05-12 10:56:57 +08:00   ❤️ 2
    fn get_name(&self) -> & str
    写成这样也许更容易理解
    fn get_name(self : &'b AA<'a> ) -> &'b str
    liangzimo
        5
    liangzimo  
    OP
       2022-05-12 11:00:22 +08:00
    @bearice 那显示添加的'a 是 n 的生命周期吗,因为只有这样编译器才会通过检查吧,这两者是怎么关联上的
    saltbo
        6
    saltbo  
       2022-05-12 11:01:15 +08:00
    @bearice 意思是 self 的生命周期并不是伴随整个 struct 的吗 出了 method 的作用于就没了?
    bearice
        7
    bearice  
       2022-05-12 11:10:49 +08:00
    @liangzimo 不是 n 的,而是指示 n 所引用的对象的。
    就相当于你告诉编译器,保证类型 AA 中的 n 所引用的值在'a 这个生命周期内都有效。
    这个周期和 类型为 AA 的变量的生命周期 是两回事儿。
    Kilerd
        8
    Kilerd  
       2022-05-12 11:25:06 +08:00   ❤️ 2
    @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
        9
    Buges  
       2022-05-12 11:27:44 +08:00 via Android
    记住生命周期是泛型参数的一种。 &'a T 是 Ref<A,T>的语法糖。
    gydi
        10
    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
        11
    liangzimo  
    OP
       2022-05-12 11:37:58 +08:00
    @bearice @Buges @Kilerd 感谢几位,理解了!
    @gydi 这个想法好,一下就通了
    libook
        12
    libook  
       2022-05-12 12:09:14 +08:00
    我也是初学者,尝试分析一下这个问题。

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

    修改后,get_name 的输出生命周期与 AA 实例一样,AA 的输入生命周期与 n 一样,那么只要 n 还活着,你就可以用 get_name 的返回值。
    libook
        13
    libook  
       2022-05-12 12:27:03 +08:00
    @bearice #7 请问我如此理解是否正确:
    方法默认的输入生命周期实际上有两个,一个是&self ,另一个是方法所对应的类型(即本题目中的结构体),生命周期省略规则的第三条只是说方法输出值的生命周期默认和&self 一样,但也默认和方法所对应的类型不同。
    bearice
        14
    bearice  
       2022-05-12 13:53:55 +08:00
    @libook 这么说也没问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3231 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 14:20 · PVG 22:20 · LAX 07:20 · JFK 10:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.