読者です 読者をやめる 読者になる 読者になる

トレイトオブジェクトのメソッド解決

Rustのこのissueで知ったが、トレイトオブジェクトのメソッド解決はやや特殊らしい。

特殊といっても特に難しいことはない。トレイトオブジェクトは、元となったトレイトを実装するが、それだけではなく、元のトレイトのメソッドを、固有メソッドとしても解決できるようになっている。

例えば以下のように動作する。トレイトメソッドの解決の一般論は過去の記事を参照。

// Fooを実装するとBarも実装される。FooとBarはm1の中にある
mod m1 {
    pub trait Foo {
        fn foo(&self) -> String;
    }
    pub trait Bar {
        fn bar(&self) -> String;
    }
    impl<T: Foo + ?Sized> Bar for T {
        fn bar(&self) -> String {
            format!("T::bar(_) from {}", self.foo())
        }
    }
}

struct A;
impl m1::Foo for A {
    fn foo(&self) -> String { String::from("<A as m1::Foo>::foo(_)") }
}

fn main() {
    // AはFooを実装している(したがってAはBarも実装している)
    let x = A;
    // FooはFooを実装している(したがってFooはBarも実装している)
    let y : &m1::Foo = &x;

    {
        // 通常、メソッド記法はトレイトがスコープ内になければ解決されない。
        use m1::Foo; // これが必要
        println!("1 {}", x.foo());
    }
    {
        // trait objectのメソッドは固有メソッドとして再定義されているかのように振る舞う。
        // use m1::Foo; // 不要
        println!("2 {}", y.foo());
    }
    {
        // Foo: Barではあるが、barはtrait Foo自身のメソッドではないため、固有メソッドにはならない。
        use m1::Bar; // これが必要
        println!("3 {}", y.bar());
    }
}

つまり、トレイトオブジェクト型 Foo は、トレイト Foo で宣言されているメソッドを固有メソッドとして再定義するFoo: Foo から誘導される他のトレイト実装のメソッドは再定義の対象とはならない。

ただし、スーパートレイト境界 (またはwhere節における Self に対する等価なトレイト境界) が指定されている場合、このスーパートレイトのメソッドも再定義の対象となる

mod m1 {
    pub trait Foo : Bar {
        fn foo(&self) -> String;
    }
    pub trait Bar {
        fn bar(&self) -> String;
    }
}
struct A;
impl m1::Foo for A {
    fn foo(&self) -> String { String::from("<A as m1::Foo>::foo(_)") }
}
impl m1::Bar for A {
    fn bar(&self) -> String { String::from("<A as m1::Bar>::bar(_)") }
}

fn main() {
    let x = A;
    let y : &m1::Foo = &x;
    {
        use m1::Foo; // これが必要
        println!("1 {}", x.foo());
    }
    {
        // use m1::Foo; // 不要
        println!("2 {}", y.foo());
    }
    {
        // use m1::Bar; // 不要
        println!("3 {}", y.bar());
    }
}

さて、この処理はコンパイラassemble_probe で行われているようだ。

    fn assemble_probe(&mut self, self_ty: Ty<'tcx>) {
        debug!("assemble_probe: self_ty={:?}", self_ty);

        match self_ty.sty {
            ty::TyDynamic(ref data, ..) => {
                if let Some(p) = data.principal() {
                    self.assemble_inherent_candidates_from_object(self_ty, p);
                    self.assemble_inherent_impl_candidates_for_type(p.def_id());
                }
            }
            ty::TyAdt(def, _) => {
                self.assemble_inherent_impl_candidates_for_type(def.did);
            }
            ...
        }
    }

このassemble_inherent_candidates_from_objectが、元トレイトのメソッドを固有メソッドとして再収集していると思われる。