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

She*_*ter 22 future rust

我正在编写一个将来的组合器,该组合器需要使用它提供的值。对于Futures 0.1,Future::polltake self: &mut Self表示有效,这意味着我的组合器包含一个OptionOption::take当基础期货结算时,我调用了它。

而是Future::poll使用标准库中的方法self: Pin<&mut Self>,因此我一直在阅读有关安全使用所需的保证的信息Pin

pin关于Drop担保的模块文档中(强调我的观点):

具体来说,对于固定数据,您必须保持不变,即从固定到调用drop之前,其内存不会失效。可以通过释放来使内存无效,也可以通过替换Some(v)byNone或调用Vec::set_len“杀死”向量中的某些元素来使其无效。

投影和结构固定(重点是我的):

固定类型时,不得提供任何其他可能导致数据从字段中移出的操作。例如,如果包装器包含一个,Option<T>并且有一个类型为take的take-like操作fn(Pin<&mut Wrapper<T>>) -> Option<T>,则该操作可用于将a 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))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

f方法由unsafe_unpinned宏生成,大致类似于:

fn f<'a>(self: Pin<&'a mut Self>) -> &'a mut Option<F> {
    unsafe { &mut Pin::get_unchecked_mut(self).f }
}
Run Code Online (Sandbox Code Playgroud)

似乎Map违反了中描述的需求pin文档,但我相信的作者Map组合子知道他们在做什么,这个代码是安全的。

什么逻辑可以使他们以安全的方式执行此操作?

Dan*_*H-M 7

这全部与结构钉扎有关。

首先,我将使用语法P<T>来的意思是这样impl Deref<Target = T>-一些(智能)指针类型PDeref::derefs到一个TPin在此类(智能)指针上,“仅”适用于/才有意义。

假设我们有:

struct Wrapper<Field> {
    field: Field,
}
Run Code Online (Sandbox Code Playgroud)

最初的问题是

我们可以得到一个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

对于每个struct字段,库作者有两种可能的选择。

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()被禁止
  • 整个结构只有DropDrop::drop不移动具有结构投影的任何字段的情况下才能实现。

  • 该结构不能是#[repr(packed)](上一项的推论)。

在您给出的future::Map示例中,这是struct future字段的情况Map

Pin该领域没有结构性预测

例如,pin_utils::unsafe_unpinned!宏用于定义此类投影(Pin<&mut Wrapper<Field>> -> &mut Field)。

在这种情况下,该字段不被视为由固定Pin<&mut Wrapper<Field>>

  • 无论FieldUnpin与否无所谓。

    • 允许使用unsafe实现将此类字段移出Pin<&mut Wrapper<Field>>。例如,Option::take()被允许
  • Drop::drop 也可以移动这些字段,

在您给出的future::Map示例中,这是struct 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
Run Code Online (Sandbox Code Playgroud)


ecs*_*rse 5

编辑:此答案不正确。它留在这里供后代使用。

让我们首先回顾一下为什么Pin要引入:为什么要静态地确保自指期货不能被移动,从而使它们的内部参考失效。

考虑到这一点,让我们看一下的定义Map

pub struct Map<Fut, F> {
    future: Fut,
    f: Option<F>,
}
Run Code Online (Sandbox Code Playgroud)

Map有两个字段,第一个存储一个future,第二个存储一个闭包,该闭包将那个future的结果映射到另一个值。我们希望支持直接存储自引用类型,future而无需将其放在指针后面。这意味着if Fut是自引用类型,Map一旦构造,就不能移动。这就是为什么我们必须Pin<&mut Map>用作的接收者Future::poll。如果Map向的实现者公开了对包含自指期货的的可变参考Future,则用户可能会通过使用Map来移动UB,从而导致UB仅使用安全代码mem::replace

但是,我们不需要支持将自引用类型存储在中f。如果我们假设a的自指部分Map完全包含在中future,则我们可以自由修改f,只要我们不允许future移动它即可。

尽管自引用闭包是非常不寻常的,但是在任何地方都没有明确声明f安全移动的假设(相当于F: Unpin)。但是,我们仍然在移动值fFuture::poll通过调用take!我认为这确实是一个错误,但我不确定100%。我认为,f()getter应该要求F: Unpin,这意味着Map只有Future在将close参数安全地从a后面移出时才能实现Pin

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