web-dev-qa-db-ja.com

固定された未来からメンバーの値を移動するのはいつ安全ですか?

提供された値を消費する必要がある将来のコンビネータを書いています。 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コンビネーターの作成者は彼らが何をしているのか知っていて、このコードは安全であると私は信じています。

安全な方法でこの操作を実行できるようにするロジックは何ですか?

23
Shepmaster

それはすべて構造的な固定に関するものです。

最初に、構文P<T>を使用してimpl Deref<Target = T>のようなものを意味します— Deref::derefsから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>>によって固定されているとは見なされません。

  • FieldUnpinであるかどうかは関係ありません。

    • 実装では、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
8
Daniel H-M

編集:この答えは不正解です。後世のためにここに残っています。

最初に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_を必要とするはずです。つまり、MapFutureを実装できるのは、クロージャー引数がPinの後ろから安全に移動できる場合のみです。

ここでpin APIの微妙な部分を見落としている可能性が非常に高く、実装は確かに安全です。私もまだ頭を抱えています。

4
ecstaticm0rse