Rust では、関数等の引数や戻り値の型からジェネリック型 T
を解決 (静的ディスパッチ) することができますが、トレイトのメソッドの引数 self
や戻り値の型 Self
から具体的な型を解決することもできます。
1. 引数 self
の型による静的ディスパッチ
まず、引数 self
の型による静的ディスパッチを確認します。
trait FooBarBaz {
fn something(&self);
}
struct Foo;
impl FooBarBaz for Foo {
fn something(&self) {
println!("Foo");
}
}
struct Bar;
impl FooBarBaz for Bar {
fn something(&self) {
println!("Bar");
}
}
struct Baz;
impl FooBarBaz for Baz {
fn something(&self) {
println!("Baz");
}
}
//
let foo = Foo;
foo.something();
Foo::something(&foo);
FooBarBaz::something(&foo); // 静的ディスパッチ: type Self = Foo;
let bar = Bar;
bar.something();
Bar::something(&bar);
FooBarBaz::something(&bar); // 静的ディスパッチ: type Self = Bar;
let baz = Baz;
baz.something();
Baz::something(&baz);
FooBarBaz::something(&baz); // 静的ディスパッチ: type Self = Baz;
※ <Foo as FooBarBaz>
のような曖昧さ回避は、構造体等の型が複数のトレイトを実装している等、メソッドが重複している場合に「トレイトを明示する」ために使うもので、本記事で言っているのは「構造体等の型を解決する」内容なので、別物です。
self
は &Self
型なので、ジェネリック型 T
を解決するのと同様に、FooBarBaz::something(&foo)
のようなトレイトのメソッド呼び出しでも具体的な構造体等の型をコンパイラが解釈することができます。
参考「Methods - Rust By Example」
参考「Performance of Code Using Generics - Generic Data Types - The Rust Programming Language」
参考「Traits - Rust By Example」
※ &self
は self: &Self
のシンタックスシュガー (「簡略表記 self
」) で、関数の引数等で参照外しに用いられる「参照パターン」ではありません。
参考「Functions - The Rust Reference」(「簡略表記 self
」、ShorthandSelf
)
参考「Reference patterns - Patterns - The Rust Reference」(「参照パターン」、ReferencePattern
)
2. 戻り値の型 Self
による静的ディスパッチ
Rust では戻り値の型からも、引数の型の場合と同様に Self
を解決することができます。
trait FooBarBaz {
fn something() -> Self;
}
struct Foo;
impl FooBarBaz for Foo {
fn something() -> Self {
println!("Foo");
Self {}
}
}
struct Bar;
impl FooBarBaz for Bar {
fn something() -> Self {
println!("Bar");
Self {}
}
}
struct Baz;
impl FooBarBaz for Baz {
fn something() -> Self {
println!("Baz");
Self {}
}
}
//
let _foo = Foo::something();
let _foo: Foo = FooBarBaz::something(); // 静的ディスパッチ: type Self = Foo;
let _bar = Bar::something();
let _bar: Bar = FooBarBaz::something(); // 静的ディスパッチ: type Self = Bar;
let _baz = Baz::something();
let _baz: Baz = FooBarBaz::something(); // 静的ディスパッチ: type Self = Baz;
参考「Disambiguating Function Calls - Call expressions - The Rust Reference」
Default::default()
や FromIterator::from_iter
メソッド等でも戻り値 Self
が使われています。
pub trait Default: Sized {
// ...
fn default() -> Self;
}
let i: i8 = Default::default(); // 静的ディスパッチ: type Self = i8;
pub trait FromIterator<A>: Sized {
// ...
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}
pub trait Iterator {
// ...
type Item;
// ...
fn collect<B: FromIterator<Self::Item>>(self) -> B
where
Self: Sized,
{
FromIterator::from_iter(self) // 静的ディスパッチ: type Self = B;
}
// ...
}
参考「Default in std::default - Rust」
参考「collect - Iterator in std::iter - Rust」
参考「FromIterator in std::iter - Rust」
3. トレイトオブジェクトによる動的ディスパッチ
トレイトオブジェクトを使うことで動的ディスパッチをすることもできます。
この場合、戻り値の型 Self
によるディスパッチはできません。
trait FooBarBaz {
fn something(&self);
}
struct Foo;
impl FooBarBaz for Foo {
fn something(&self) {
println!("Foo");
}
}
struct Bar;
impl FooBarBaz for Bar {
fn something(&self) {
println!("Bar");
}
}
struct Baz;
impl FooBarBaz for Baz {
fn something(&self) {
println!("Baz");
}
}
//
let foo_bar_baz: &dyn FooBarBaz = &Foo;
foo_bar_baz.something(); // 動的ディスパッチ: type Self = Foo;
FooBarBaz::something(foo_bar_baz); // 動的ディスパッチ: type Self = Foo;
let foo_bar_baz: &dyn FooBarBaz = &Bar;
foo_bar_baz.something(); // 動的ディスパッチ: type Self = Bar;
FooBarBaz::something(foo_bar_baz); // 動的ディスパッチ: type Self = Bar;
let foo_bar_baz: &dyn FooBarBaz = &Baz;
foo_bar_baz.something(); // 動的ディスパッチ: type Self = Baz;
FooBarBaz::something(foo_bar_baz); // 動的ディスパッチ: type Self = Baz;
// Note: Rust でトレイトオブジェクトをダウンキャストするには少し手間が必要で、
// 以下のように単純に as で変換することは不可。
// すなわち、FooBarBaz トレイトを実装するメソッド以外は簡単には呼べない。
//
// (foo_bar_baz as &Foo).something();
// Foo::something(foo_bar_baz as &Foo);
トレイトオブジェクトへの変換は、Unsized 型強制によるものです。
// ... 略
let foo_bar_baz: &dyn FooBarBaz = &Foo; // Unsized 型強制: &Foo -> &dyn FooBarBaz
// ... 略