提供された値を消費する必要がある将来のコンビネータを書いています。 futures 0.1では、 _Future::poll
_ は_self: &mut Self
_を取得しました。これは、コンビネーターにOption
が含まれていることを意味し、基になるフューチャーが解決するときに_Option::take
_を呼び出しました。
標準ライブラリの _Future::poll
_ メソッドは代わりに_self: Pin<&mut Self>
_を取るので、Pin
を安全に使用するために必要な保証について読んでいます。
pin
ギャランティ (強調鉱山)に関するDrop
モジュールのドキュメントから:
具体的には、固定されたデータの場合、固定された瞬間からdropが呼び出されるまで、メモリが無効化されないという不変条件を維持する必要があります。メモリは、割り当て解除によって無効化できますが、
Some(v)
をNone
で置き換えるか、または_Vec::set_len
_を呼び出して「kill」することによっても無効にできますベクトルの一部の要素。
そして プロジェクションと構造的ピンニング (私の強調):
タイプが固定されているときにフィールドからデータが移動する可能性のある他の操作を提供してはなりません。たとえば、ラッパーに_
Option<T>
_が含まれ、タイプがfn(Pin<&mut Wrapper<T>>) -> Option<T>
のtake-like operationがある場合、その操作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
コンビネーターの作成者は彼らが何をしているのか知っていて、このコードは安全であると私は信じています。
安全な方法でこの操作を実行できるようにするロジックは何ですか?
それはすべて構造的な固定に関するものです。
最初に、構文P<T>
を使用してimpl Deref<Target = T>
のようなものを意味します— Deref::deref
sからP
へのT
の(スマート)ポインター型。 Pin
は、そのような(スマート)ポインターにのみ「適用」/意味を持ちます。
私たちが持っているとしましょう:
struct Wrapper<Field> {
field: Field,
}
最初の質問は
Pin<P<Field>>
をWrapper
からfield
に「投影」することにより、Pin<P<Wrapper<Field>>>
からPin<P<_>>
を取得できますか?
これには、基本的なプロジェクションP<Wrapper<Field>> -> P<Field>
が必要です。これは以下の場合にのみ可能です。
共有参照(P<T> = &T
)。 Pin<P<T>>
が常に deref
sからT
であることを考えると、これはそれほど興味深いケースではありません。
一意の参照(P<T> = &mut T
)。
このタイプのプロジェクションには、構文&[mut] T
を使用します。
質問は次のようになります。
Pin<&[mut] Wrapper<Field>>
からPin<&[mut] Field>
に移動できますか?
ドキュメントから不明確かもしれない点は、決定するのはWrapper
の作成者次第であるということです!
ライブラリの作成者は、構造体フィールドごとに2つの選択肢があります。
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
編集:この答えは不正解です。後世のためにここに残っています。
最初にPin
が導入された理由を思い出してみましょう。自己参照先物を移動できないように静的に保証し、内部参照を無効にします。
それを念頭に置いて、Map
の定義を見てみましょう。
_pub struct Map<Fut, F> {
future: Fut,
f: Option<F>,
}
_
Map
には2つのフィールドがあり、1つ目はフューチャーを格納し、2つ目はそのフューチャーの結果を別の値にマップするクロージャーを格納します。自己参照型をポインタの背後に配置することなく、future
に直接格納できるようにしたいと考えています。つまり、Fut
が自己参照型の場合、Map
は一度作成すると移動できません。そのため、_Pin<&mut Map>
_のレシーバーとして_Future::poll
_を使用する必要があります。自己参照フューチャを含むMap
への通常の可変参照がFuture
の実装者に公開された場合、ユーザーはMap
を_mem::replace
_を使用して移動させることにより、安全なコードのみを使用してUBを発生させることができます。
ただし、f
への自己参照型の格納をサポートする必要はありません。 Map
の自己参照部分が完全にfuture
に含まれていると想定する場合、f
の移動を許可しない限り、future
を自由に変更できます。
自己参照クロージャは非常に珍しいものですが、f
が安全に移動できるという想定(これは_F: Unpin
_と同等です)は明示的に述べられていません。ただし、f
を呼び出すことにより、_Future::poll
_のtake
の値を引き続き移動します。これは確かにバグだと思いますが、100%確実ではありません。 f()
ゲッターは_F: Unpin
_を必要とするはずです。つまり、Map
がFuture
を実装できるのは、クロージャー引数がPin
の後ろから安全に移動できる場合のみです。
ここでpin APIの微妙な部分を見落としている可能性が非常に高く、実装は確かに安全です。私もまだ頭を抱えています。