在带有生命周期参数的函数调用中,结构体参数的生命周期如何处理?

5

这段代码可以通过编译器(为了澄清,生命周期没有省略):

struct Foo<'a> {
    _field: &'a i32,
}

fn test<'a, 'b, 'c>(_x: &'a mut Foo<'c>, _y: &'b bool) {  // case 1
}

fn main() {
    let f = &mut Foo { _field: &0 };
    {
        let p = false;
        test(f, &p);
    }
}

如果我在test的定义中使用'b而不是'c,就像这样:
fn test<'a, 'b>(_x: &'a mut Foo<'b>, _y: &'b bool) {  // case 2
}

代码无法编译(“p does not live long enough”)!

在情况2中,我期望在调用test时发生以下情况:

  • 'a被设置为f的实际生命周期,
  • 'b被设置为Foo的实际生命周期和&p的实际生命周期的交集,即&p的生命周期,

一切都应该很好,就像情况1一样。

然而,在情况2中实际发生的事情似乎是'b被强制成Foo的生命周期,这对于&p的生命周期来说太大了,因此编译器会报错“p does not live long enough”。是这样吗?

更奇怪的是(情况3):只有在test采用&mut时才会失败。如果我保留<'b>,但删除mut,就像这样:

fn test<'a, 'b>(_x: &'a Foo<'b>, _y: &'b bool) {  // case 3
}

代码再次通过。

有人能解释一下吗?

谢谢。


这篇文章已经在 Rust 用户论坛上进行了交叉发布 - Shepmaster
1个回答

4

注意与 mut 的区别是一个关键观察点。我认为,如果你改变第二个参数的类型并给出一种可能的实现,它会更有意义:

fn test<'a, 'b>(_x: &'a mut Foo<'b>, _y: &'b i32) {
    _x._field = _y;
}

该函数具有改变 _x 的能力。这种变化也包括在 _field存储新的引用。但是,如果我们能够存储具有更短寿命的引用 (您提到的交叉点),则在内部块结束时,Foo 中的引用将变为无效状态,违反了Rust对内存安全性的保证!

使用不可变引用时,您不会面临这种危险,因此编译器允许使用。

你发现了一个重要的问题——Rust并不总是关心你在函数中做什么。在检查函数调用是否有效时,仅使用函数的类型签名。

我相信有一种花哨的方法可以使用适当的术语来表达这个概念,如逆变协变,但我不熟悉它们,无法正确使用!^_^


非常感谢shepmaster!这真的很明确。我在Rust用户论坛上收到了类似的提示(不像你的那么详细),并且能够在那里对所有3种情况进行简要总结。 - dacker
2
在情况1中,由于存在&mut,'c获得了Foo的原始生命周期。'b是独立的,不会造成任何影响。在情况2中,同样由于存在&mut,'b获得了Foo的原始生命周期。这次,&p“太短”无法适应&'b bool,因此出现“p does not live long enough”的错误。在情况3中,'b被允许缩短为&p的生命周期,因为没有危险使test突变为比原始Foo更短的引用。希望我理解得正确。 - dacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接