【问题标题】:Why is an explicit lifetime needed in this Rust function output?为什么这个 Rust 函数输出需要明确的生命周期?
【发布时间】:2021-07-10 07:24:33
【问题描述】:

我有一个Config 结构来存储全局配置。配置需要传递给不同的结构(抽象层)。为了配置一致性和节省内存,结构存储对全局配置的引用而不是副本。

然后我有如下实现:Playground

/// Global Config
#[derive(Debug)]
struct Config {
    pub version: String,
}

/// Layer A
#[derive(Debug)]
struct A<'cfg> {
    id: u32,
    config: &'cfg Config,
}

/// Layer B
#[derive(Debug)]
struct B<'cfg> {
    a_id: u32,
    b_id: u32,
    config: &'cfg Config,
}

impl<'cfg> A<'cfg> {
    pub fn new(id: u32, config: &Config) -> A {
        A { id, config }
    }
    
    #[allow(unused)]
    pub fn version(&self) {
        println!("{}_a{}", self.config.version, self.id);
    }

    pub fn create_many_b(&self) -> Vec<B<'cfg>> { // <-- Removing explicit `'cfg` lifetime for B here makes complier complain
        let cfg = self.config;
        let mut res = Vec::new();
        for id in 0..10u32 {
            res.push(B { a_id: self.id, b_id: id, config: cfg });
        }
        res
    }
}

impl<'cfg> B<'cfg> {
    pub fn version(&self) {
        println!("{}_a{}+b{}", self.config.version, self.a_id, self.b_id);
    }
}

fn create_many_a(config: &Config) -> Vec<A> {
    let mut res = Vec::new();
    for id in 0..5u32 {
        res.push(A::new(id, config));
    }
    res
}

fn main() {
    // Create a global config
    let cfg = Config { version: "1.0".to_owned() };
    println!("cfg pointer: {:p}", &cfg);
    // Use the global config to create many `A`s
    let a_vec = create_many_a(&cfg);

    let mut b_vec = Vec::new();
    // then create many `B`s
    for a in a_vec.into_iter() {
        b_vec.extend(a.create_many_b())
    }
    for b in b_vec.iter() {
        b.version();
    }
}

正如我的代码注释所说,编译器在删除生命周期标记 (playground) 时拒绝构建。我的问题是为什么需要在函数create_many_b 中为 B 提供明确的生命周期标记。在这个例子中,生命周期省略是如何工作的?由A 通过函数create_many_b 创建的Bs 不应该与A 具有相同的生命周期,而'cfg 的生命周期已经从Config 获得了'cfg 生命周期?

【问题讨论】:

    标签: rust lifetime


    【解决方案1】:

    这些配方

    pub fn create_many_b(&self) -> Vec<B>
    fn create_many_a(config: &Config) -> Vec<A>
    

    编译器推断结果中缺少的生命周期信息与作为参数给出的引用的生命周期相同。 在create_many_a() 的情况下这是正确的,因为missing 生命周期正是config 的生命周期(这是我们想要的)。 另一方面,在create_many_b() 的情况下,我们不希望Bs 包含一个生命周期为selfA)生命周期的引用,但我们希望使用的生命周期由这个A里面的config成员。

    因此,写作

    impl<'cfg> A<'cfg> {
       ...
    pub fn create_many_b(&self) -> Vec<B<'cfg>>
    

    明确指出用于B 的生命周期信息与用于A 的生命周期信息相同。 self 引用的 A 可以消失,B 内部的引用仍然有效,因为它不依赖于 A 本身,而是依赖于 Config 所依赖的 A

    main() 函数中

    for a in a_vec.into_iter() {
    

    引入了一个A,它将在每次迭代时消失(它们是从a_vec消耗),因此如果它们依赖于@987654344,则创建的Bs 不能持续超过此迭代@,但如果它们依赖于 cfg,则它们可以在此循环之后仍然存在。

    如果你写过

    for a in a_vec.iter() {
    

    (仍然缺少&lt;'cfg&gt; 注释)它会被编译器接受,但它会产生误导,因为b_vec 中的Bs 依赖于As 中的a_vec 和不是直接在cfg 上,这是struct B&lt;'cfg&gt; {... 定义中的意图。

    【讨论】:

      【解决方案2】:

      使用省略的生命周期,编译器将推断生命周期为:

      pub fn create_many_b<'a>(&'a self) -> Vec<B<'a>>;
      

      这意味着向量中的Bs 可能不会超过调用者对self 的引用。

      如果您从self拥有的数据中借用,这将是正确的生命周期:删除self 后数据将无效。但是数据是从config 借来的,它不属于A,因此您可以通过在数据实际来自的生命周期中表达来减少限制:

      pub fn create_many_b(&self) -> Vec<B<'cfg>>;
      

      通常您不会注意到这两个签名的区别,但是您对这些类型的使用实际上需要更宽松的生命周期。特别是,a_vec.into_iter() 消耗 a。这与推断的生命周期冲突,因为您在使用 a 之后在向量中使用了 Bs。

      无论A 发生了什么,只要config 未被删除,使用限制较少的生命周期即可使用Bs。

      【讨论】:

        【解决方案3】:

        当您删除生命周期注释时,编译器将通过填充间隙来推断它。像这样的:

        pub fn create_many_b<'a>(&'a self) -> Vec<B<'a>> {
        

        编译器假设每个B 的寿命与对self 的引用一样长。

        当你调用这个函数时:

        for a in a_vec.into_iter() {
          b_vec.extend(a.create_many_b())
        } <- "a" is dropped here
        

        你应该有一个 Bs 的 Vector,其中每个都持有对 a 的引用,此时已删除。

        因此,基本上,通过删除生命周期注释,您是在告诉编译器推断它,在这种情况下,编译器将根据它在函数中保存的引用填充生命周期,而不是 A 的生命周期。

        我希望这有助于理解您收到的错误消息。

        【讨论】:

          猜你喜欢
          • 2023-04-11
          • 2015-10-15
          • 2014-11-16
          • 2022-11-21
          • 1970-01-01
          • 2017-07-04
          • 1970-01-01
          • 2017-08-18
          • 2017-04-11
          相关资源
          最近更新 更多