`std :: mem :: drop`が上位のトレイト境界のクロージャ| _ |()とまったく同じではないのはなぜですか?


13

の実装はstd::mem::drop次のように文書化されています。

pub fn drop<T>(_x: T) { }

そのため、クロージャー|_| ()(通称、トイレクロージャーdropは、双方向での1対1の交換の可能性があると予想します。ただし、以下のコードdropは、これが関数のパラメーターにバインドされたより高いランクの特性と互換性がないことを示していますが、トイレのクロージャーは互換性があります。

fn foo<F, T>(f: F, x: T)
where
    for<'a> F: FnOnce(&'a T),
{
    dbg!(f(&x));
}

fn main() {
    foo(|_| (), "toilet closure"); // this compiles
    foo(drop, "drop"); // this does not!
}

コンパイラのエラーメッセージ:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^
   |     |
   |     expected signature of `for<'a> fn(&'a _) -> _`
   |     found signature of `fn(_) -> _`

error[E0271]: type mismatch resolving `for<'a> <fn(_) {std::mem::drop::<_>} as std::ops::FnOnce<(&'a _,)>>::Output == ()`
  --> src/main.rs:10:5
   |
1  | fn foo<F, T>(f: F, x: T)
   |    ---
2  | where
3  |     for<'a> F: FnOnce(&'a T),
   |                ------------- required by this bound in `foo`
...
10 |     foo(drop, "drop"); // this does not!
   |     ^^^ expected bound lifetime parameter 'a, found concrete lifetime

drop任意のサイズのに関して一般的であると考えられることを考えるとT、「より一般的な」シグネチャfn(_) -> _がと互換性がないことは不合理に聞こえfor<'a> fn (&'a _) -> _ます。コンパイラがdropここの署名を認めないのはなぜですか?トイレのクロージャが代わりに配置されている場合、それは何が違うのですか?

回答:


4

問題の核心は、それdropが単一の関数ではなく、それぞれが特定のタイプをドロップする関数のパラメーター化されたセットです。上位のトレイトバウンド(以降hrtb)を満たすには、単一の任意の有効期間の型への参照を同時に取得できる関数がです。


使用します dropジェネリック関数の典型的な例としてしますが、これはより一般的にも当てはまります。参照用のコードは次のとおりfn drop<T>(_: T) {}です。

概念的にdropは、は単一の関数ではなく、考えられるすべての型に対して1つの関数ですT。の特定のインスタンスはdrop、単一の型の引数のみを取ります。これは単形化と呼ばれます。で別のものTを使用するdropと、別のバージョンのdropがコンパイルされます。そのため、ジェネリック関数を引数として渡してその関数を一般的に使用することはできません(この質問を参照)

一方、などの関数fn pass(x: &i32) -> &i32 {x}はhrtbを満たしfor<'a> Fn(&'a i32) -> &'a i32ます。異なり、drop我々は、単一同時に満たす機能Fn(&'a i32) -> &'a i32のために、すべての生涯を'a。使い方にpassも反映されています。

fn pass(x: &i32) -> &i32 {
    x
}

fn two_uses<F>(f: F)
where
    for<'a> F: Fn(&'a i32) -> &'a i32, // By the way, this can simply be written
                                       // F: Fn(&i32) -> &i32 due to lifetime elision rules.
                                       // That applies to your original example too.
{
    {
        // x has some lifetime 'a
        let x = &22;
        println!("{}", f(x));
        // 'a ends around here
    }
    {
        // y has some lifetime 'b
        let y = &23;
        println!("{}", f(y));
        // 'b ends around here
    }
    // 'a and 'b are unrelated since they have no overlap
}

fn main() {
    two_uses(pass);
}

(遊び場)

この例では、ライフタイム'a'bは互いに関係がありません。どちらも完全にもう一方を包含していません。したがって、ここではサブタイプの処理は行われていません。の単一のインスタンスpassが、実際には2つの異なる、無関係なライフタイムで使用されています。

これがをdrop満たさない理由for<'a> FnOnce(&'a T)です。の特定のインスタンスは、1つのライフタイムdropのみをカバーできます(サブタイプを無視)。私たちが渡された場合に(わずかな署名の変更やコンパイラが私たちを聞かせてと仮定して)上記の例から、それはいくつかの特定の寿命を選択しなければならないとのインスタンスの範囲内となり、いくつかのための具体的な寿命を。この関数は単一のライフタイムにのみ適用されるため、無関係な2つのライフタイムで使用することはできません。droptwo_uses'adroptwo_usesFn(&'a i32)'a'a

では、なぜトイレのクロージャーはHRTBを取得するのでしょうか?クロージャーのタイプを推論するときに、期待されるタイプがより高いランクの特性境界が必要であることを示唆している場合、コンパイラーは1つの適合を試みます。この場合、成功します。


問題#41078はこれと密接に関連しており、特に、ここでのeddybのコメントは基本的に上記の説明を提供します(ただし、通常の関数ではなく、クロージャーのコンテキストでは)。ただし、問題自体は現在の問題には対応していません。代わりに、それを使用する前に変数にトイレのクロージャーを割り当てるとどうなるかを扱います(試してみてください!)。

将来状況が変わる可能性はありますが、ジェネリック関数のモノモーフィング方法をかなり大きく変更する必要があります。


4

要するに、両方の行が失敗するはずです。ただし、hrtbの有効期間を処理する古い方法の1つのステップ、つまりリークチェックには現在健全性の問題があるため、一方rustcが(誤って)受け入れられ、もう一方がかなり悪いエラーメッセージのままになります。

でリークチェックを無効にするとrustc +nightly -Zno-leak-check、より実用的なエラーメッセージが表示されます。

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(drop, "drop");
   |     ^^^ one type is more general than the other
   |
   = note: expected type `std::ops::FnOnce<(&'a &str,)>`
              found type `std::ops::FnOnce<(&&str,)>`

このエラーの私の解釈は&xfoo関数の本体にはスコープ本体に限定されたスコープライフタイムしかないためf(&x)、同じスコープライフタイムもありfor<'a>、特性の境界に必要な普遍的な数量化を満足できない可能性があるということです。

ここで提示する質問は、問題#57642とほぼ同じです。これには、2つの対照的な部分もあります。

hrtbの有効期間を処理する新しい方法は、いわゆるユニバースを使用することです。Nikoは、宇宙のリークチェックに取り組むWIPを持っています。この新しい体制では、上記の問題#57642の両方の部分がすべて失敗し、診断がはるかに明確になるとされています。それまでに、コンパイラーがサンプル・コードを正しく処理できるはずだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.