【问题标题】:Why does borrow checker complain about the lifetimes of these different slices?为什么借用检查器抱怨这些不同切片的生命周期?
【发布时间】:2021-04-02 13:51:34
【问题描述】:

为什么,在下面的代码中,使用数组切片有效,但使用 Vec 的切片无效?

use rand::{rngs::adapter::ReadRng, RngCore};
use std::io::Read;

struct MyRng {
    rng: Box<dyn RngCore>,
}

pub fn main() {
    // Version 1: error
    //
    let data = Vec::<u8>::new();
    let data_slice = data.as_slice();

    // Version 2: works
    //
    // let data_slice = &[0_u8][..];

    // Version 3: error (!?!)
    //
    // let data = [0_u8];
    // let data_slice = &data[..];

    let read = Box::new(data_slice) as Box<dyn Read>;
    let rng = Box::new(ReadRng::new(read));

    // With this commented out, all versions work.
    MyRng { rng };
}

有几件事让我感到困惑:

  • 如果这三种方法都在同一个范围内,有什么区别?
  • 错误提示 data 在借用时被删除,但指向范围的末尾 - 到那时不是所有内容都被删除了吗?
  • 为什么如果我删除 MyRng 实例化,一切正常?

【问题讨论】:

    标签: vector rust slice lifetime borrow-checker


    【解决方案1】:

    The Rust Reference on Lifetime Elision:

    如果特征没有生命周期边界,则生命周期在表达式中推断,并且在表达式之外是 'static

    所以默认情况下,装箱的 trait 对象会得到一个 'static 绑定。所以这个结构:

    struct MyRng {
        rng: Box<dyn RngCore>,
    }
    

    其实展开成这样:

    struct MyRng {
        rng: Box<dyn RngCore + 'static>,
    }
    

    这会迫使您生成自有类型或 'static 引用以满足界限。但是,您可以通过使您的结构通用化来完全退出隐式 'static 绑定,然后编译所有不同版本的代码:

    use rand::{rngs::adapter::ReadRng, RngCore};
    use std::io::Read;
    
    struct MyRng<'a> {
        rng: Box<dyn RngCore + 'a>,
    }
    
    pub fn main() {
        // Version 1: now works
        let data = Vec::<u8>::new();
        let data_slice = data.as_slice();
        let read = Box::new(data_slice) as Box<dyn Read>;
        let rng = Box::new(ReadRng::new(read));
        let my_rng = MyRng { rng };
    
        // Version 2: still works
        let data_slice = &[0_u8][..];
        let read = Box::new(data_slice) as Box<dyn Read>;
        let rng = Box::new(ReadRng::new(read));
        let my_rng = MyRng { rng };
    
        // Version 3: now works
        let data = [0_u8];
        let data_slice = &data[..];
        let read = Box::new(data_slice) as Box<dyn Read>;
        let rng = Box::new(ReadRng::new(read));
        let my_rng = MyRng { rng };
    }
    

    playground


    更直接地回答您的个人问题:

    如果都在同一个范围内,这三种方法有什么区别?

    作用域和生命周期不是一回事,但是这 3 种方法的主要区别在于方法 #2 创建了一个静态切片。当您将一些 &amp;T 引用硬编码到代码中而不引用任何变量拥有的任何数据时,它会被写入二进制文件的只读段并获得 'static 生命周期。

    错误表示数据在借用时被丢弃,但指向范围的末尾 - 到那时不是所有东西都被丢弃了吗?

    是的,但是根据定义,您的类型要求传递的值受'static 生命周期的限制,并且由于方法#1 和#3 不会产生这样的值,因此编译器会拒绝该代码。

    为什么如果我删除 MyRng 实例化,一切正常?

    因为您的MyRng 结构的定义没有任何问题,只有当您尝试不正确地实例化它时,编译器才会抱怨。

    【讨论】:

      【解决方案2】:

      [0_u8] 是一个常量,所以它经历了rvalue static promotion,允许对它的引用有一个静态的生命周期,所以它不会被丢弃。您的代码失败,因为您有一个借用局部变量 data 的切片,但尝试从中创建一个 MyRng 变量。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-01-14
        • 1970-01-01
        • 2022-06-29
        • 1970-01-01
        • 1970-01-01
        • 2016-11-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多