什么时候可以将会员价值从固定的未来中转移出来?

时间:2019-05-09 11:33:42

标签: rust future

我正在编写一个将来的组合器,该组合器需要使用它提供的值。对于期货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组合者的作者知道他们正在执行并且该代码是安全的。

什么逻辑允许他们安全地执行此操作?

2 个答案:

答案 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)的假设在任何地方都没有明确说明。但是,我们仍然通过调用fFuture::poll中的take中移动值!我认为这确实是一个错误,但我不确定100%。我认为f()的getter应该要求F: Unpin,这意味着Map仅在可以安全地从Future后面移出闭包参数时才能实现Pin

很可能我在这里忽略了pin API中的一些细微之处,并且实现确实是安全的。我仍然还在缠着头。

答案 1 :(得分:4)

这与结构钉扎有关。

首先,我将使用语法P<T>来表示类似impl Deref<Target = T>的某些内容-一些{智能}指针类型P,其中Deref::deref指向TPin仅对此类(智能)指针“应用” /有意义。

让我们说:

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>>总是derefT,这不是一个很有趣的情况。

  • 唯一引用(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