Closed3

notes for rust-lang/rust#72614

Yuki OkushiYuki Okushi

source: https://github.com/rust-lang/rust/issues/72614

これ何

なぜ言及されているコードのコンパイルが通るのかoli-obk大先生が解説してくれたので日本語で噛み砕く

前提

以下のRustコードを考える (playground):

pub trait Foo {
    type Bar: std::ops::Add<Self::Bar, Output = Self::Bar> + std::fmt::Debug;
    fn bar(self) -> Self::Bar;
}

impl<T: std::ops::Add<T, Output = T> + std::fmt::Debug> Foo for T {
    type Bar = T;
    fn bar(self) -> Self::Bar {
        self
    }
}

pub fn foo() -> impl Foo<Bar = impl std::ops::Sub<usize>> {
    5usize
}

fn baz<T: Foo>(foo1: T, foo2: T) {
    dbg!(foo1.bar() + foo2.bar()); // result: foo1.bar() + foo2.bar() = 10
}

fn main() {
    baz(foo(), foo());
}

  • Foo::Bar 型は std::ops::Add<> をトレイト境界として要求している
  • foo 関数は Bar 型を impl std::ops::Sub<usize> とするような返り値を持っている
    • ここで std::ops::Add のトレイトを実装していないこの返り値はエラーとされるべき
  • baz関数はFooを実装している型を受け取ってAddするような中身
  • main関数ではbaz関数にfoo関数を渡している
    • つまり、impl Foo<Bar = impl std::ops::Sub<usize>>を満たす何か
    • その"何か"に実装されているbarメソッドを呼び出している
  • impl Sub 型で宣言されていないトレイト実装がリークしている
    • barメソッドはFooトレイトに実装されているはずなので
Yuki OkushiYuki Okushi

"Obligation"の説明:

An Obligation represents some trait reference (e.g., i32: Eq) for which the “impl_source” must be found. The process of finding an “impl_source” is called “resolving” the Obligation. This process consists of either identifying an impl (e.g., impl Eq for i32) that satisfies the obligation, or else finding a bound that is in scope.

  • "Obligation"はi32: Eqのような“impl_source”を必須とする、あるトレイト参照
  • “impl_source”を見つけるプロセスをObligationを"resolve"すると呼ぶ
  • "resolving"はobligationを満たすimpl Eq for i32のようなimplを特定するか、スコープ内にある境界を見つける作業
Yuki OkushiYuki Okushi

解説

rustcではfoo関数の返り値のような、function signatureにあるopaque typesを推論用変数に置き換えている

つまり以下を例に取ると:

pub fn foo() -> impl Foo<Bar = impl std::ops::Sub<usize>> {
    5usize
}
  1. impl Sub<usize>_1: Sub<usize> という obligation を持つ _1 という変数になる
  2. つまり、impl Foo<Bar = impl Sub<usize>>impl Foo<Bar = _1> となる
  3. 1と同様に impl Foo_2: Foo という obligation を持つ _2 という変数になる
  4. 2と同様に置き換えると <_2 as Foo>::Bar = _1 (implから) や <_2 as Foo>::Bar: Add<<_2 as Foo>::Bar> (トレイト定義から) となる
  • 関数の返り値を見るとusizeで、_2usizeであることを確かめる必要がある
    • usize: Foo<usize as Foo>::Bar = _1 を証明できればよい

これは:

impl<T: std::ops::Add<T>> Foo for T {
    type Bar = T;
    fn bar(self) -> Self::Bar {
        self
    }
}

の部分を見ると簡単に証明できる(usizeは元々Addトレイトを実装しているため上記2つが成り立つ)

  • 上記のimplから<usize as Foo>::Bar = usizeと分かる
  • 今あるobligationsは<_2 as Foo>:: Bar = _1_2 = usizeで、つまり<usize as Foo>::Bar = _1
  • これにより_1 = usize
  • さらにトレイトから<_2 as Foo>::Bar: Add<<_2 as Foo>::Bar>ということがわかっている
  • これにより <usize as Foo>::Bar: Add<<usize as Foo>::Bar> という風にresolveできる
  • 以上によってusize: Add<usize>が成立する
このスクラップは2022/08/09にクローズされました