什么时候将成员价值从固定的未来中移出是安全的?
When is it safe to move a member value out of a pinned future?
我正在编写一个未来的组合器,它需要使用它提供的值。对于 futures 0.1,Future::poll
采用 self: &mut Self
,这实际上意味着我的组合器包含一个 Option
,当潜在的 future 解决时我调用了 Option::take
。
标准库中的 Future::poll
方法取而代之的是 self: Pin<&mut Self>
,所以我一直在阅读有关安全使用 Pin
.[=43 所需的保证的内容=]
来自 Drop
guarantee 上的 pin
模块文档(强调我的):
Concretely, for pinned data you have to maintain the invariant that its memory will not get invalidated from the moment it gets pinned until when drop is called. Memory can be invalidated by deallocation, but also by replacing a Some(v)
by None
, or calling Vec::set_len
to "kill" some elements off of a vector.
和Projections and Structural Pinning(强调我的):
You must not offer any other operations that could lead to data being moved out of the fields when your type is pinned. For example, if the wrapper contains an Option<T>
and there is a take-like operation with type fn(Pin<&mut Wrapper<T>>) -> Option<T>
, that operation can be used to move a T
out of a pinned Wrapper<T>
-- which means pinning cannot be structural.
但是,现有的 Map
组合器会在基础 future 已解析时对成员值调用 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
组合器知道他们在做什么并且这段代码是安全的。
什么逻辑允许他们以安全的方式执行此操作?
编辑:这个答案不正确。它留在这里留给子孙后代。
让我们首先回顾一下为什么首先引入 Pin
:我们想静态地确保 self-referential 期货不能被移动,从而使它们的内部引用无效。
考虑到这一点,让我们看一下Map
的定义。
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
Map
有两个字段,第一个存储未来,第二个存储将未来结果映射到另一个值的闭包。我们希望支持直接在 future
中存储 self-referential 类型,而不是将它们放在指针后面。这意味着如果 Fut
是 self-referential 类型,则 Map
一旦构造就不能移动。这就是为什么我们必须使用 Pin<&mut Map>
作为 Future::poll
的接收者。如果对包含 self-referential 未来的 Map
的正常可变引用曾经暴露给 Future
的实现者,用户可以通过使 Map
使用 mem::replace
移动。
但是,我们不需要支持在 f
中存储 self-referential 类型。如果我们假设一个Map
的self-referential部分完全包含在future
中,我们可以随意修改f
,只要不允许future
待搬家。
虽然 self-referential 闭包非常不寻常,但并未在任何地方明确说明 f
可以安全移动(相当于 F: Unpin
)的假设。但是,我们仍然通过调用take
将f
中的值移动到Future::poll
中!我认为这确实是一个错误,但我不是 100% 确定。我认为 f()
getter 应该需要 F: Unpin
这意味着 Map
只能在闭包参数可以安全地从 [= 后面移动时实现 Future
11=].
很可能我在这里忽略了 pin API 中的一些细微之处,实现确实是安全的。我还在苦思冥想。
一切都与结构固定有关。
首先,我将使用语法 P<T>
表示类似 impl Deref<Target = T>
的意思 — 某些(智能)指针类型 P
Deref::deref
指向 T
. Pin
只有 "applies" 对这样的(智能)指针有意义。
假设我们有:
struct Wrapper<Field> {
field: Field,
}
最初的问题是
Can we get a Pin<P<Field>>
from a Pin<P<Wrapper<Field>>>
, by "projecting" our Pin<P<_>>
from the Wrapper
to its field
?
这需要基本投影 P<Wrapper<Field>> -> P<Field>
,这仅适用于:
共享引用 (P<T> = &T
)。这不是一个非常有趣的案例,因为 Pin<P<T>>
总是 deref
到 T
。
唯一引用(P<T> = &mut T
)。
对于这种类型的投影,我将使用语法 &[mut] T
。
现在的问题变成:
Can we go from Pin<&[mut] Wrapper<Field>>
to 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
示例中,Map
结构的 future
字段就是这种情况。
没有结构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
示例中,Map
结构的 f
字段就是这种情况。
两种投影的示例
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
我正在编写一个未来的组合器,它需要使用它提供的值。对于 futures 0.1,Future::poll
采用 self: &mut Self
,这实际上意味着我的组合器包含一个 Option
,当潜在的 future 解决时我调用了 Option::take
。
标准库中的 Future::poll
方法取而代之的是 self: Pin<&mut Self>
,所以我一直在阅读有关安全使用 Pin
.[=43 所需的保证的内容=]
来自 Drop
guarantee 上的 pin
模块文档(强调我的):
Concretely, for pinned data you have to maintain the invariant that its memory will not get invalidated from the moment it gets pinned until when drop is called. Memory can be invalidated by deallocation, but also by replacing a
Some(v)
byNone
, or callingVec::set_len
to "kill" some elements off of a vector.
和Projections and Structural Pinning(强调我的):
You must not offer any other operations that could lead to data being moved out of the fields when your type is pinned. For example, if the wrapper contains an
Option<T>
and there is a take-like operation with typefn(Pin<&mut Wrapper<T>>) -> Option<T>
, that operation can be used to move aT
out of a pinnedWrapper<T>
-- which means pinning cannot be structural.
但是,现有的 Map
组合器会在基础 future 已解析时对成员值调用 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
组合器知道他们在做什么并且这段代码是安全的。
什么逻辑允许他们以安全的方式执行此操作?
编辑:这个答案不正确。它留在这里留给子孙后代。
让我们首先回顾一下为什么首先引入 Pin
:我们想静态地确保 self-referential 期货不能被移动,从而使它们的内部引用无效。
考虑到这一点,让我们看一下Map
的定义。
pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
Map
有两个字段,第一个存储未来,第二个存储将未来结果映射到另一个值的闭包。我们希望支持直接在 future
中存储 self-referential 类型,而不是将它们放在指针后面。这意味着如果 Fut
是 self-referential 类型,则 Map
一旦构造就不能移动。这就是为什么我们必须使用 Pin<&mut Map>
作为 Future::poll
的接收者。如果对包含 self-referential 未来的 Map
的正常可变引用曾经暴露给 Future
的实现者,用户可以通过使 Map
使用 mem::replace
移动。
但是,我们不需要支持在 f
中存储 self-referential 类型。如果我们假设一个Map
的self-referential部分完全包含在future
中,我们可以随意修改f
,只要不允许future
待搬家。
虽然 self-referential 闭包非常不寻常,但并未在任何地方明确说明 f
可以安全移动(相当于 F: Unpin
)的假设。但是,我们仍然通过调用take
将f
中的值移动到Future::poll
中!我认为这确实是一个错误,但我不是 100% 确定。我认为 f()
getter 应该需要 F: Unpin
这意味着 Map
只能在闭包参数可以安全地从 [= 后面移动时实现 Future
11=].
很可能我在这里忽略了 pin API 中的一些细微之处,实现确实是安全的。我还在苦思冥想。
一切都与结构固定有关。
首先,我将使用语法 P<T>
表示类似 impl Deref<Target = T>
的意思 — 某些(智能)指针类型 P
Deref::deref
指向 T
. Pin
只有 "applies" 对这样的(智能)指针有意义。
假设我们有:
struct Wrapper<Field> {
field: Field,
}
最初的问题是
Can we get a
Pin<P<Field>>
from aPin<P<Wrapper<Field>>>
, by "projecting" ourPin<P<_>>
from theWrapper
to itsfield
?
这需要基本投影 P<Wrapper<Field>> -> P<Field>
,这仅适用于:
共享引用 (
P<T> = &T
)。这不是一个非常有趣的案例,因为Pin<P<T>>
总是deref
到T
。唯一引用(
P<T> = &mut T
)。
对于这种类型的投影,我将使用语法 &[mut] T
。
现在的问题变成:
Can we go from
Pin<&[mut] Wrapper<Field>>
toPin<&[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
示例中,Map
结构的 future
字段就是这种情况。
没有结构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
示例中,Map
结构的 f
字段就是这种情况。
两种投影的示例
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