我正在编写一个将来的组合器,该组合器需要使用它提供的值。对于期货0.1,Future::poll
取了self: &mut Self
,这实际上意味着我的组合器包含一个Option
,当基础期货结算时,我在其上调用了Option::take
。
标准库中的Future::poll
方法取而代之的是self: Pin<&mut Self>
,因此我一直在阅读有关安全使用Pin
所需的保证的信息。
来自Drop
guarantee上的pin
模块文档(重点是我的):
具体来说,对于固定的数据,必须保持不变,即从固定到调用drop为止,其内存不会失效。可以通过释放来使内存无效,也可以通过将
Some(v)
替换为None
,或调用Vec::set_len
来“杀死”向量中的某些元素。 / p>
和Projections and Structural Pinning(重点是我):
在固定类型时,您不得提供可能导致数据从字段中移出的任何其他操作。例如,如果包装器包含一个
Option<T>
,并且有一个类型为fn(Pin<&mut Wrapper<T>>) -> Option<T>
的 take-like操作,则可以使用该操作将T
移出固定的Wrapper<T>
的含义-这意味着固定不能是结构性的。
但是,当潜在的未来已解决时,现有的Map
组合器会在成员值上调用Option::take
:
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
match self.as_mut().future().poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(output) => {
let f = self.f().take()
.expect("Map must not be polled after it returned `Poll::Ready`");
Poll::Ready(f(output))
}
}
}
f
方法是由unsafe_unpinned
宏生成的,大致类似于:
fn f<'a>(self: Pin<&'a mut Self>) -> &'a mut Option<F> {
unsafe { &mut Pin::get_unchecked_mut(self).f }
}
似乎 Map
违反了pin
文档中描述的要求,但是我相信Map
组合者的作者知道他们正在执行并且该代码是安全的。
什么逻辑允许他们安全地执行此操作?
答案 0 :(得分:5)
编辑:此答案可能不正确,并且绝对不完整,仅供后人参考。
让我们首先回顾一下为什么引入Pin
的原因:我们要静态地确保自指期货不会被移动,从而使它们的内部参考失效。
牢记这一点,让我们看一下Map
的定义。
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
Map
有两个字段,第一个存储一个Future,第二个存储一个闭包,该闭包将那个Future的结果映射到另一个值。我们希望支持将自引用类型直接存储在future
中,而不必将它们放在指针后面。这意味着如果Fut
是自引用类型,则Map
一旦构造就无法移动。这就是为什么我们必须使用Pin<&mut Map>
作为Future::poll
的接收者。如果向Map
的实现者公开了对包含自指期货的Future
的正常可变引用,则用户可能通过移动Map
来导致UB仅使用安全代码来导致UB。使用mem::replace
。
但是,我们不需要支持将自引用类型存储在f
中。如果我们假设Map
的自指部分完全包含在future
中,则我们可以自由修改f
,只要我们不允许future
感动。
尽管自引用闭包非常不寻常,但是f
可以安全移动(相当于F: Unpin
)的假设在任何地方都没有明确说明。但是,我们仍然通过调用f
在Future::poll
中的take
中移动值!我认为这确实是一个错误,但我不确定100%。我认为f()
的getter应该要求F: Unpin
,这意味着Map
仅在可以安全地从Future
后面移出闭包参数时才能实现Pin
。
很可能我在这里忽略了pin API中的一些细微之处,并且实现确实是安全的。我仍然还在缠着头。
答案 1 :(得分:4)
这与结构钉扎有关。
首先,我将使用语法P<T>
来表示类似impl Deref<Target = T>
的某些内容-一些{智能}指针类型P
,其中Deref::deref
指向T
。 Pin
仅对此类(智能)指针“应用” /有意义。
让我们说:
struct Wrapper<Field> {
field: Field,
}
最初的问题是
能否通过将
Pin<P<Field>>
从Pin<P<Wrapper<Field>>>
投射到Pin<P<_>>
来从Wrapper
获得field
?
这需要基本投影P<Wrapper<Field>> -> P<Field>
,这仅适用于:
共享参考(P<T> = &T
)。鉴于Pin<P<T>>
总是deref
到T
,这不是一个很有趣的情况。
唯一引用(P<T> = &mut T
)。
对于这种类型的投影,我将使用语法&[mut] T
。
问题现在变成:
我们可以从
Pin<&[mut] Wrapper<Field>>
转到Pin<&[mut] Field>
吗?
文档中可能不清楚的一点是,Wrapper
的创建者必须做出决定!
对于每个结构字段,库作者有两种可能的选择。
Pin
投影例如,pin_utils::unsafe_pinned!
宏用于定义这种投影(Pin<&mut Wrapper<Field>> -> Pin<&mut Field>
)。
要使Pin
投影声音正确:
只有当所有具有结构化Unpin
投影的字段都实现Pin
时,整个结构才必须实现Unpin
。
unsafe
将此类字段移出Pin<&mut Wrapper<Field>>
(或Pin<&mut Self>
时移出Self = Wrapper<Field>
)。例如, Option::take()
被禁止。仅当Drop
不移动有结构投影的任何字段时,整个结构才可以实现Drop::drop
。
结构不能为#[repr(packed)]
(上一项的推论)。
在给定的future::Map
示例中,future
结构的Map
字段就是这种情况。
Pin
投影例如,pin_utils::unsafe_unpinned!
宏用于定义这种投影(Pin<&mut Wrapper<Field>> -> &mut Field
)。
在这种情况下,该字段不被Pin<&mut Wrapper<Field>>
固定。
Field
是否为Unpin
没关系。
unsafe
将此类字段移出Pin<&mut Wrapper<Field>>
。例如,允许 Option::take()
。 Drop::drop
移动此类字段,
在给定的future::Map
示例中,f
结构的Map
字段就是这种情况。
impl<Fut, F> Map<Fut, F> {
unsafe_pinned!(future: Fut); // pin projection -----+
unsafe_unpinned!(f: Option<F>); // not pinned --+ |
// | |
// ... | |
// | |
fn poll (mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
// | |
match self.as_mut().future().poll(cx) { // <----+ required here
Poll::Pending => Poll::Pending, // |
Poll::Ready(output) => { // |
let f = self.f().take() // <--------+ allows this