LoginSignup
2
3

More than 1 year has passed since last update.

[Rust] 引数 self や戻り値の型 Self に基づくメソッドディスパッチ

Last updated at Posted at 2021-08-24

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

&selfself: &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 が使われています。

Default::default メソッドのシグネチャ
pub trait Default: Sized {
    // ...
    fn default() -> Self;
}
Default::default() の使用例
let i: i8 = Default::default(); // 静的ディスパッチ: type Self = i8;
FromIterator::from_iter メソッドのシグネチャ
pub trait FromIterator<A>: Sized {
    // ...
    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}
Iterator::collect メソッドのデフォルト定義
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);

参考「Trait Objects Perform Dynamic Dispatch - Using Trait Objects That Allow for Values of Different Types - The Rust Programming Language

トレイトオブジェクトへの変換は、Unsized 型強制によるものです。

// ... 略
let foo_bar_baz: &dyn FooBarBaz = &Foo; // Unsized 型強制: &Foo -> &dyn FooBarBaz
// ... 略

参考「Unsized Coercions - Type coercions - The Rust Reference

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3