我正在编写一个将来的组合器,该组合器需要使用它提供的值。对于Futures 0.1,Future::polltake self: &mut Self表示有效,这意味着我的组合器包含一个Option,Option::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>,则该操作可用于将aT移出固定的位置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组合子知道他们在做什么,这个代码是安全的。
什么逻辑可以使他们以安全的方式执行此操作?
这全部与结构钉扎有关。
首先,我将使用语法P<T>来的意思是这样impl Deref<Target = T>-一些(智能)指针类型P是Deref::derefs到一个T。Pin在此类(智能)指针上,“仅”适用于/才有意义。
假设我们有:
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()被禁止。整个结构只有Drop在Drop::drop不移动具有结构投影的任何字段的情况下才能实现。
该结构不能是#[repr(packed)](上一项的推论)。
在您给出的future::Map示例中,这是struct 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示例中,这是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)
编辑:此答案不正确。它留在这里供后代使用。
让我们首先回顾一下为什么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)。但是,我们仍然在移动值f在Future::poll通过调用take!我认为这确实是一个错误,但我不确定100%。我认为,f()getter应该要求F: Unpin,这意味着Map只有Future在将close参数安全地从a后面移出时才能实现Pin。
我很有可能在这里忽略了pin API中的一些细微之处,并且实现确实是安全的。我也仍然围绕着它。
| 归档时间: |
|
| 查看次数: |
388 次 |
| 最近记录: |