为什么在这个 Rust 函数输出中需要明确的生命周期?

Why is an explicit lifetime needed in this Rust function output?

我有一个 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 创建的 B 不应该与 A 具有相同的生命周期吗? 'cfgConfig?

有了这些公式

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

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

中使用生命周期

因此,写作

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 消耗 ),因此创建的 B 不能如果它们依赖于 a,则持续时间比本次迭代更长,但如果它们依赖于 cfg,则它们可以在本次循环后仍然存在。

如果你写了

for a in a_vec.iter() {

(仍然缺少 <'cfg> 注释)它会被编译器接受,但它会产生误导,因为 b_vec 中的 B 取决于 As 在 a_vec 内而不是直接在 cfg 上,这是 struct B<'cfg> {....

定义中的意图

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

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 的引用,此时已被删除。

所以,基本上,通过删除生命周期注解,您是在告诉编译器推断它,在这种情况下,编译器将根据它在函数中持有的引用来填充生命周期,而不是[=16 的生命周期=].

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

如果生命周期被省略,编译器将推断生命周期为:

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

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

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

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

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

只要 config 没有被删除,使用限制较少的生命周期就可以使用 B,而不管 A.[=25= 发生了什么]