在 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 <: b <=> lifetime b is no longer than lifetime a
。
我认为this 讨论可能有用。
@mwhittaker:如果你没记错的话,这将与 Rust 中为生命周期定义“子类型”的方式相反。
@sellibitze,我认为你是对的! Rust 和 Cyclone 中子类型的定义似乎是相反的。这是我之前链接的论文的摘录,它使 Cyclone 的子类型规则相当明确:“我们观察到,如果对应于p1
的区域比对应于p2
的区域寿命长,那么使用@987654333 类型的值是合理的@ 我们期望 *p2
" 类型之一
链接的 GitHub 问题的解决方案是,“生命周期不是类型,因此没有子类型顺序;但是,它们确实有一个由区域包含给出的顺序。”
【参考方案1】:
免责声明:我不完全是 CS 大师,所以这个答案将侧重于实际概念,我什至不会尝试将其与理论概念联系起来,以免我把事情弄得一团糟。 p>
我认为问题在于试图将子类型概念应用于不是类型的东西。
'a
是一生
&'a T
是一个类型
您可以比较&'a T
和&'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 <: lesser
当用作约束时:lesser <: greater
注意:一些生命周期可以同时作为保证和约束。
【讨论】:
我猜这里的关键词是协方差(如果F
是协变的,那么a <: b
暗示F(a) <: F(b)
)和逆变(如果F
是逆变的,a <: b
暗示F(b) <: F(a)
) .
@Rhymoid:谢谢! (我倾向于永远不记得哪个是哪个......)以上是关于在 Rust 中与生命周期的子类型关系作斗争的主要内容,如果未能解决你的问题,请参考以下文章