トレイト
Rustにクラスやプロトタイプチェーンは存在しない。そのかわり、トレイトを使ってオブジェクト指向プログラミングを実現する。トレイトはメソッドを集めたものだ。トレイトを型に実装することによって、その型の値がメソッドを提供するようになる。
トレイトの定義
say_hello
メソッドを提供するSayHello
トレイトを作ってみよう。
trait SayHello : std::fmt::Display {
fn say_hello(&self) {
println!("Hello. This is {}.", self);
}
}
std::fmt::Display
とは何かというと、これもトレイトだ。SayHello : std::fmt::Display
というのは、SayHello
トレイトを実装するには、まずstd::fmt::Display
トレイトを実装する必要があるということを表している。これは、整形表示するのに必要なトレイトだ。
say_hello
メソッドの第一引数は&self
となっている。これは、レシーバーへのリファレンスを受け取ることを表している。say_hello
メソッドにはデフォルトの実装が定義されている。デフォルトの実装は必ずしも定義されなくても良いが、その場合は、型にトレイトを実装する際に定義する必要がある。
トレイトの実装
impl
を使って、型にトレイトを実装できる。
impl SayHello for i32 {}
impl SayHello for str {
fn say_hello(&self) {
println!("Hi. I'm {}.", self);
}
}
str
型については、デフォルトと異なる実装を定義している。
使ってみる。
trait SayHello : std::fmt::Display {
fn say_hello(&self) {
println!("Hello. This is {}.", self);
}
}
impl SayHello for i32 {}
impl SayHello for str {
fn say_hello(&self) {
println!("Hi. I'm {}.", self);
}
}
fn main() {
42.say_hello();
"Alice".say_hello();
}
Hello. This is 42.
Hi. I'm Alice.
トレイト制約
ジェネリックの型変数がトレイトを実装していることを表したいときは、トレイト制約を使う。
fn greeting<T: SayHello>(x: T) {
x.say_hello();
}
<T: SayHello>
の部分が、型変数T
はSayHello
を実装していることを表す。
+
で、型が複数のトレイトを実装していることを表すことができる。
fn greeting<T: SayHello + SayGoodbye>(x: T) {
x.say_hello();
x.say_goodbye();
}
where
節を使った書き方もできる。
fn greeting<T>(x: T) where T: SayHello + SayGoodbye {
x.say_hello();
x.say_goodbye();
}
Uniform Function Call Syntax
メソッドは関数として使うこともできる。
fn greeting<T>(x: T) where T: SayHello + SayGoodbye {
<T as SayHello>::say_hello(&x);
<T as SayGoodbye>::say_goodbye(&x);
}
<T as R>::method(args)
という書き方は、型T
が実装しているトレイトR
のメソッドmethod
を関数として書いたものだ。このような記法(UFCS)は、複数のトレイトが同名のメソッドを提供していた場合に、曖昧さを無くすために使うことが出来る。
Sized
トレイト
Sized
は特別なトレイトで、str
や後述のオブジェクト型のような、型からサイズが決定できない型以外が実装しており、暗黙のうちに型変数の型制約として加わっている。型変数がstr
やトレイト型を取ることができるようにするためには、この型制約を取り除かなければならない。そのための記法が?Sized
だ。
リファレンス型に対してSayHelloメソッドを実装する。
impl<'a, T: ?Sized + SayHello> SayHello for &'a T {
fn say_hello(&self) {
<T as SayHello>::say_hello(self);
}
}
このように定義することで、&str
型もSayHello
トレイトを実装していることになる。
トレイトオブジェクト
トレイトオブジェクトは、仮想メソッドテーブル(vtable)を使った動的ディスパッチができる型だ。トレイトR
を実装した型T
へのリファレンス&T
やBox<T>
を&R
あるいはBox<R>
にキャストすることで、トレイトオブジェクトに変換できる。
Box::new(42) as Box<SayHello>
異なる型のデータも、トレイトオブジェクトに変換することで同じコンテナーに入れられるようになる。
trait SayHello : std::fmt::Display {
fn say_hello(&self) {
println!("Hello. This is {}.", self);
}
}
impl SayHello for i32 {}
impl SayHello for str {
fn say_hello(&self) {
println!("Hi. I'm {}.", self);
}
}
impl<'a, T: ?Sized + SayHello> SayHello for &'a T {
fn say_hello(&self) {
<T as SayHello>::say_hello(self);
}
}
fn main() {
let v = vec![
Box::new(42) as Box<SayHello>,
Box::new("Alice") as Box<SayHello>,
];
for x in &v {
x.say_hello();
}
}
**オブジェクト安全なトレイトについてのみトレイトオブジェクトに変換できる。**バイナリメソッド(レシーバー以外の引数にSelf型の値をとるメソッド)を持つトレイトなどは、オブジェクト安全でないので、トレイトオブジェクトに変換できない。
fn main() {
let _ = Box::new(42) as Box<std::cmp::Eq>; // ERROR
}
オブジェクト安全の詳しい定義はオブジェクト安全#Detailed designを参照すること。