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

rust 生命周期错误

  •  
  •   giiiiiithub · 68 天前 · 1079 次点击
    这是一个创建于 68 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以下代码会抛出异常。不知道该怎么理解,有没有懂的老哥?

    fn main() {
        {
            let a = mpsc::channel();
            let y:Sender<&str> = a.0;
            let x = String::from("abc");
            let s = &(x[..]);
            y.send(s);
        }
    }
    

    异常信息:

    error[E0597]: `x` does not live long enough
     --> src\main.rs:6:19
      |
    5 |         let x = String::from("abc");
      |             - binding `x` declared here
    6 |         let s = &(x[..]);
      |                   ^ borrowed value does not live long enough
    7 |         y.send(s);
    8 |     }
      |     -
      |     |
      |     `x` dropped here while still borrowed
      |     borrow might be used here, when `y` is dropped and runs the destructor for type `Sender<&str>`
      |
      = note: values in a scope are dropped in the opposite order they are defined
    
    24 条回复    2024-02-21 14:32:12 +08:00
    nagisaushio
        1
    nagisaushio  
       68 天前 via Android
    最后的 note 说了:x 会比 a 先 drop ,所以把定义反过来就好了
    https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=76ac154fc9043bb360d366abb07918fe
    giiiiiithub
        2
    giiiiiithub  
    OP
       68 天前
    @nagisaushio 可是为什么 x 会比 a 先 drop ?
    nagisaushio
        3
    nagisaushio  
       67 天前 via Android
    @giiiiiithub 规定先定义的后 drop
    araraloren
        4
    araraloren  
       67 天前
    This is called compiler error, not exception.
    giiiiiithub
        5
    giiiiiithub  
    OP
       67 天前
    @nagisaushio 可是把顺序不变,y 换成下边的代码,也是正常:

    ```
    #[derive(Debug)]
    struct Foo;

    impl Foo {
    fn send(&self, t: &str) {
    println!("{}", t)
    }
    }

    fn main() {
    {
    // let a = mpsc::channel();
    // let y:Sender<&str> = a.0;

    let y = Foo;
    let x = String::from("abc");
    let s = &(x[..]);
    y.send(s);
    }
    }
    ```
    giiiiiithub
        6
    giiiiiithub  
    OP
       67 天前
    @araraloren 是的,多语言,脑子切换不及时
    nagisaushio
        7
    nagisaushio  
       67 天前 via Android
    @giiiiiithub 你这个 Foo.send 完没有持有&str 所有权
    giiiiiithub
        8
    giiiiiithub  
    OP
       67 天前
    @nagisaushio 老哥,Channel 的 Sender 是咋获取到的?这俩传参是一样的
    nagisaushio
        9
    nagisaushio  
       67 天前
    要把入参存在 Foo 内部,并且 Foo 要实现 Drop trait 。更符合 Sender 的模拟是这样的

    https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9cdb7165f175525b2a88f9902563083b
    giiiiiithub
        10
    giiiiiithub  
    OP
       67 天前
    @nagisaushio 感谢,我简化了一下你的代码,也理解了其中一部分原委。这份代码编译错误,一部分是因为 Foo 实例的生命周期和被发送给&str 被标记了同样的生命周期'a 。

    但是我还有不解:
    1. 这份代码如果不实现 Drop 接口也能编译并运行通过,根本原因是什么?不实现 Drop 接口就不会释放吗?
    giiiiiithub
        11
    giiiiiithub  
    OP
       67 天前
    @nagisaushio 不好意思,忘记贴代码了: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9645f22c4704aa689612a16a0cb8c022

    具体来讲 send 方法内部不需要 self.0 = t; 也可以复现同样编译错误。
    buxudashi
        12
    buxudashi  
       67 天前
    看下面 2 种的不同。
    你的:
    let mut y = Foo::default();
    let x = String::from("abc");
    let s = &(x[..]);
    y.send(s);
    第 2 种:
    let x = String::from("abc");//这句放前面。
    let mut y = Foo::default();

    let s = &(x[..]);
    y.send(s);

    程序的运行就是栈。跟洗盘子类似。先进后出。先放的盘子最后洗。
    你的那个先放 y,x,s ,所以 X 要先弹出的。s 只是个引用。x 弹出了引用就没意义了。

    你了解下 cpu 的加减法四则运算的栈方式。
    buxudashi
        13
    buxudashi  
       67 天前
    更精确点的描述是,当 abc 弄到堆上时,把 abc 的地址和 Len/cap 组成三合一的胖指针 x 压到栈里。

    接着取这个胖指针的引用(不是 abc 的),弹出胖指针 x ,把 s 压到栈里。但这时 s 已经无效了。
    giiiiiithub
        14
    giiiiiithub  
    OP
       67 天前
    @buxudashi

    这段代码要编译出错必须有几个条件:
    1. x 必须在 y 之后创建
    2. send 方法必须是&mut self ,不能是&mut self
    3. 必须实现 Drop trait

    你这个解释也只是解释了为什么有第一个条件
    giiiiiithub
        15
    giiiiiithub  
    OP
       67 天前
    @buxudashi 手误,修改第二条:2. send 方法必须是&mut self ,不能是& self
    buxudashi
        16
    buxudashi  
       67 天前
    试了一下,果然你说的是存在的。加了&mut ,输入的&str 估计编译器觉得你会用来改东西。毕竟用的是&mut.

    这里应该是 drop 那的影响。你把 drop 删了有无 mut 也正常了。

    这说明 y 有 mut 改写,所有参与改写的引用都得活着。
    giiiiiithub
        17
    giiiiiithub  
    OP
       67 天前
    @buxudashi 我感觉 rust 难的一点是,好像没有文档系统性地讲这些底层逻辑和关系。不知道它到底都有哪些原则。本来以为自己掌握了生命周期,咔咔来几个例子,又懵了。 我觉得这不是语言本身的问题,好像是文档的问题。(也可能是我没找到好的文档
    giiiiiithub
        18
    giiiiiithub  
    OP
       66 天前
    @nagisaushio 我把代码做了一个最小简化,并对生命周期做了一点展开。尝试分析一下原因。你看下对不对。

    代码: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2de7b9f132bcea057bd22e4b66b966fa

    1. 在 send 方法上,str 和 self 有同样的生命周期标记,并且 self 是&mut 。这就要求 str 的生命周期必须等于或长于 self 的生命周期。为什么? 因为 rust 看到 self 是&mut ,认为方法内 self 可能会依赖于 str ,比如有 self.0=str 这样的代码,编译器为了保证安全,必须做出这样的假设。这也解释了为什么 self 必须是&mut self ,而&self 不会产生编译错误,因为&self 是不可变的,并不会产生 self.0=str 这样的代码。

    2. 之前的代码,Drop trait 只是表象,真正的原因是发生了借用。我最新的代码 main 函数最后一行是 let y = & foo; 这个借用行为,导致 rust 认为,foo 实例相关的依赖的生命周期也至少和 foo 实例的生命周期一样长,而这些依赖,也包含了 send 方法中的 str 。原因如上所说,rust 会假设 send 方法内有:self.0=str ,这样的代码。 但是很明显,传给 str 的 def 在 let y = & foo 之前,被丢弃了,于是导致 rust 假设的 self.0=str 这样的代码无法编译通过。
    PTLin
        19
    PTLin  
       66 天前
    你这个其实不算生命周期问题,是 drop check 的问题。主要就是保证值析构时,析构函数不会访问到悬垂引用。

    Sender 里 send 的行为可能是这样:send 时把值存在了内部某个地方,drop 的时候可能对这个值进行了某些操作,这种情况下这个值必然不能先于 Sender 被 drop ,所以调下 drop 顺序就能解决。

    你这个 case 里 drop 另一个影响是,你在 send 后尝试对字符串进行 push 这种借出可变引用的行为也会报错,无论你是否调换了顺序。

    可以看看这两个: https://doc.rust-lang.org/nomicon/dropck.html
    https://ng6qpa7sht.feishu.cn/docx/LGnVdlqwGoIUpuxUMWRcptEbndd
    PTLin
        20
    PTLin  
       66 天前
    附带一提,你这个例子里是 Sender 中的 SenderFlavor 实现了 Drop ,所以你在文档里看不到 Sender 实现了 Drop ,并且假如这个类型没实现 Drop ,就不会触发 Drop check ,你这个代码也就没问题。
    giiiiiithub
        21
    giiiiiithub  
    OP
       66 天前
    @PTLin 已经在 18 楼给出了解释,是多个原因综合的结果。不单纯是 drop 的问题,drop 只是表象。
    giiiiiithub
        22
    giiiiiithub  
    OP
       66 天前
    @PTLin 另外,mpmc 的 Sender 实现了 drop ,mpsc 的 Sender 依赖于 mpmc 的 Sender
    PTLin
        23
    PTLin  
       66 天前
    @giiiiiithub 你标题的问题就是 Drop check 的问题,你 18 楼的问题是更复杂的生命周期协变 逆变的问题
    https://doc.rust-lang.org/nomicon/subtyping.html
    https://rustcc.cn/article?id=38000317-0d25-40b1-aee9-81143322bec4

    另外,你标题代码没有 use ,我没以为是 mpmc 。
    giiiiiithub
        24
    giiiiiithub  
    OP
       66 天前
    @PTLin

    😓

    @nagisaushio 给出了等效的构造,这个编译错误要出现,必须有几个条件同时满足,drop check 也只是表象。只说是 drop check 相当于只讲表皮原因。已经在 18 楼解释过了,不对是不是 drop check 再做解释了。除非有新的更为深入的解释。

    如果使用生命周期的协变和逆变解释会更为麻烦。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   885 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 20:56 · PVG 04:56 · LAX 13:56 · JFK 16:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.