作者提出了之前关于固定位置设计的一个变体,该变体更符合 Rust 的现有特性集。
先前设计中最奇特的方面是“固定字段”的概念,它支持固定投影。这与 Rust 中字段投影的正常工作方式非常不同:如果有对结构体的可变引用,可以获得其字段的可变引用。(作者知道 Niko Matsakis 最近探索了可能改变这一点的想法,但本文不会深入考虑该提案。)作者提出了一个具有类似属性的设计,而不是引入一种字段标记。
首先,固定引用应像其他引用一样支持投影。它们不支持的唯一原因是与Drop
相关的不健全性,作者之前在这篇文章中讨论过。解决此问题的方法是说,只要通过的类型满足以下条件,固定引用就支持投影:
- 如果实现了
Unpin
,则使用自动特性机制实现,而不是手动编写impl
。 - 如果实现了
Drop
,要么实现Unpin
,要么其析构函数使用fn drop(&pin mut self)
签名。
只要类型满足这些要求,在安全代码中就无法违反固定保证。(本文的早期版本以不同且错误的方式陈述了这些标准,因为作者忘记了Unpin
必须使用自动特性机制实现才能保持健全性。)
然而,仍然需要某种方式来支持未固定字段,这些字段是应用于整个对象的固定契约的例外。为此,该语言将引入一种新的“单元”类型UnpinCell
,它“取消固定”其中的任何对象。UnpinCell
的 API 可能如下所示:
pub struct UnpinCell<T>(T);
impl<T> UnpinCell<T> {
pub fn new(value: T) -> UnpinCell<T> {
UnpinCell(value)
}
pub fn into_inner(self) -> T {
self.0
}
}
// 即使 T:!Unpin
impl<T> Unpin for UnpinCell<T> { }
impl<T> Deref for UnpinCell<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for UnpinCell<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
这个极其简单的 API 允许通过固定指针对UnpinCell
内部的值进行可变访问,即使该值未实现Unpin
。这是合理的,因为UnpinCell
创建了一个屏障,通过该屏障无法进行固定投影,因此单元内部的对象永远不会被视为固定的。
重新审视之前文章中的MaybeDone
示例,现在它将如下所示:
enum MaybeDone<F: Future> {
Polling(F),
Done(UnpinCell<Option<F::Output>>),
}
impl<F: Future> MaybeDone<F> {
fn maybe_poll(&pin mut self, cx: &mut Context<'_>) {
if let MaybeDone::Polling(fut) = self {
if let Poll::Ready(res) = fut.poll(cx) {
*self = MaybeDone::Done(UnpinCell::new(Some(res)));
}
}
}
fn is_done(&self) -> bool {
matches!(self, &MaybeDone::Done(_))
}
fn take_output(&pin mut self) -> Option<F::Output> {
// res: &pin mut UnpinCell<Option<F::Output>>
if let MaybeDone::Done(res) = self {
// 两个解引用可变强制转换将其解析为 Option::take
res.take()
} else {
None
}
}
}
(作者已更新语法,使用pin
而不是pinned
,因为这是该项目的固定性实验使用的语法。)
这种组合使固定位置成为对语言的更微小更改:固定引用的行为类似于普通引用,要获取作为异常的字段,有一个类似单元的 API 用于“内部不可固定性”,就像“内部可变性”一样。在作者看来,这优于字段修饰符,因为它不会引入任何新的语言特性类别。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。