在 Rust 中与生命周期的子类型关系作斗争

Posted

技术标签:

【中文标题】在 Rust 中与生命周期的子类型关系作斗争【英文标题】:Struggling with the subtyping relation of lifetimes in Rust 【发布时间】:2014-10-13 23:35:31 【问题描述】:

我多次浏览了 Rust 文档的 marker section 和关于 subtyping 和 variance 的***文章,却没有提高我对生命周期子类型关系的理解,我感到很愚蠢。

我想我只是习惯了“典型的 OOP 风格”子关系,例如“Cat <: animal t s>

但这如何适用于生命周期?现在在 Rust 中定义的方式显然是(*)

(#1) 'a <:> 生命周期 a 不长于生命周期 b。

你可能会想“当然就是这个意思!”可能是因为 <:>子类型吗?让我们尝试应用 Wikipedia 对子类型关系的定义:

(#2) 'a <:> 生命周期 a 可以安全地用于预期生命周期 b 的上下文中。

我的问题是我无法协调这一点。你如何从#2 到#1?因为对我来说,这似乎是一个矛盾...如果您期望某物至少在 b 内还活着,并且您的某物的生命期 a 比 b 短,那么您显然不能在某物的生命期为 b 的情况下使用它是必需的,可以吗?是我一个人,还是我们把一生的子类型关系弄错了?

编辑:(*) 根据 #rust IRC 频道中的 Ms2ger 所说,情况就是这样。它还符合在 Items 迭代器中使用的逆变生命周期标记的文档。

Edit2:ConvariantLifetime 和 CovariantLifetime 标记已被移除。我们现在在 marker 模块中替换了 PhantomData

【问题讨论】:

Rust 的生命周期概念的灵感来自另一种称为 Cyclone 的编程语言及其类似的区域概念。 Region-Based Memory Management in Cyclone 的第 2.3 节讨论了区域子类型化,可能会有所帮助!我相信a &lt;: b &lt;=&gt; lifetime b is no longer than lifetime a 我认为this 讨论可能有用。 @mwhittaker:如果你没记错的话,这将与 Rust 中为生命周期定义“子类型”的方式相反。 @sellibitze,我认为你是对的! Rust 和 Cyclone 中子类型的定义似乎是相反的。这是我之前链接的论文的摘录,它使 Cyclone 的子类型规则相当明确:“我们观察到,如果对应于p1 的区域比对应于p2 的区域寿命长,那么使用@987654333 类型的值是合理的@ 我们期望 *p2" 类型之一 链接的 GitHub 问题的解决方案是,“生命周期不是类型,因此没有子类型顺序;但是,它们确实有一个由区域包含给出的顺序。” 【参考方案1】:

免责声明:我不完全是 CS 大师,所以这个答案将侧重于实际概念,我什至不会尝试将其与理论概念联系起来,以免我把事情弄得一团糟。 p>

我认为问题在于试图将子类型概念应用于不是类型的东西。

'a 是一生 &amp;'a T 是一个类型

您可以比较&amp;'a T&amp;'b U,看看它们是否遵循子类型关系,但您无法在摘要中建立具有两个生命周期的子类型关系,因为:

有时,为了可替换,新的生命周期必须大于被替换的生命周期。 有时,为了可替换,新的生命周期必须小于被替换的生命周期。

我们可以通过两个简单的例子来验证这一点。


第一个例子可能是最简单的:如果生命周期更大,可以替换它!

//  Using a lifetime as a bound
struct Reference<'a, T>
    where T: 'a

    data: &'a T


fn switch<'a, 'b, T>(r: &mut Reference<'a, T>, new: &'b T)
    where 'b: 'a

    r.data = new;

这里,编译器仅在 'b 至少与 'a 一样大时才允许替换,'a 由生命周期限制 'b: 'a 表示。这是因为 Rust 厌恶悬空引用,因此容器可能只包含对比它寿命更长的对象的引用。

当用作保证时,较长的生命周期是较短生命周期的子类型,可以用它来代替。这暗示了@aturon 提到的,在这种用法中'static 是所有生命周期的子类型。


第二个例子有点棘手:如果生命周期更短,则可以替换它!

让我们从以下开始:

struct Token;

fn restrict<'a, 'b, T>(original: &'a T, _: &'b Token) -> &'b T
    where 'a: 'b

    original

以下用法正确:

fn main() 
    let i = 4;

    
        let lesser = Token;
        let k = restrict(&i, &lesser);
        println!("", k);
    

我们之前的演示说我们可以用更长的寿命代替更短的寿命:

fn main() 
    let greater = Token;
    let j;  // prevent unification of lifetimes

    
        let i = 4;
        j = restrict(&i, &greater);
    
    println!("", j);


error: `i` does not live long enough
j = restrict(&i, &greater);

当用作约束时,较短的生命周期是较长生命周期的子类型,可以被替换。在这种用法中,'static 是所有生命周期的超类型。


因此,生命周期之间没有单一的子类型关系,因为它们服务于两个完全相反的目的!

总结一下:

当用作保证时:greater &lt;: lesser 当用作约束时:lesser &lt;: greater

注意:一些生命周期可以同时作为保证和约束。

【讨论】:

我猜这里的关键词是协方差(如果F是协变的,那么a &lt;: b暗示F(a) &lt;: F(b))和逆变(如果F是逆变的,a &lt;: b暗示F(b) &lt;: F(a)) . @Rhymoid:谢谢! (我倾向于永远不记得哪个是哪个......)

以上是关于在 Rust 中与生命周期的子类型关系作斗争的主要内容,如果未能解决你的问题,请参考以下文章

Spring中与bean有关的生命周期

我啥时候需要在 Rust 中指定显式生命周期?

使用字符串时 Rust 中的生命周期

软件架构设计与原则

什么是非词汇生命周期?

在 Rust 中使用带有结构的生命周期的正确方法是啥?