トレイトはコードを(自分やチーム的に)分かりやすい単位で再利用する為の仕組みです。例えばあるトレイトIs
はこうです。
trait Is {
fn is_true(&self) -> bool;
fn is_false(&self) -> bool {
self.is_true()
};
}
これは::is_true
と::is_false
メソッドに関する情報を保ってます。特に::is_false
は::is_true
と真逆を期待するメソッドになので、この段階で中身を実装できます。
今、以下のような Struct A
とB
があるとします。
struct A(i32);
struct B {
value: i32,
}
もし、この2つがIs
の機能がほしい時は以下のように実装します。まずA
。
impl Is for A {
fn is_true(&self) -> bool {
self.0 > 0
}
}
次にB
。
impl Is for B {
fn is_true(&self) -> bool {
self.value > 0
}
}
どちらも::is_false
メソッドを実装してませんが、トレイト定義時に実装済のものは省略できるルールがある為です。以下を実行しちゃんと実装されてる事が確認できます。
let a = A(33);
assert_eq!(a.is_true(), true);
assert_eq!(a.is_false(), true);
let b = B { value: 33 };
assert_eq!(b.is_true(), true);
assert_eq!(b.is_false(), true);
このようにIs
を実装した Struct には、その中の各メソッドが使える状態なことが分かりました。
ジェネリック
例えば以下の関数があるとします。これはA
の参照を受け、その::is_true
の結果を返すだけの関数です。
fn is_true_for_a(s: &A) -> bool {
s.is_true()
}
assert_eq!(is_true_for_a(&a), true);
見てきたように::is_true
を実装してるのはA
だけではありません。B
にも欲しくなりました。
fn is_true_for_b(s: &B) -> bool {
s.is_true()
}
assert_eq!(is_true_for_b(&b), true);
もし、同じくIs
なC
があるならまた繰り返し…でもいいですが、ジェネリックという仕組みを使うと1回で済ませられます。
fn is_true(s: &T) -> bool {
s.is_true()
}
assert_eq!(is_true(&a), true);
assert_eq!(is_true(&b), true);
関数is_true
が期待するs
はIs
を実装済みのものに限るという意味になる為です。
ちなみにこれはコンパイル時に以下のような形になるようです。
fn is_true_for_a(s: &A) -> bool {
s.is_true()
}
fn is_true_for_b(s: &B) -> bool {
s.is_true()
}
ジェネリック前に戻りました!
is_true_for_a
のような書き換えられた関数を Specialized Function (特殊化された関数)、関数が複数になる事を code bloat (コードの膨張)、そして実行時に全ての扱う値が分かってる状態で行われるこの仕組みを Static Dispatch (静的ディスパッチ)と呼ぶようです。
トレイトオブジェクト
オブジェクト安全なトレイトです。これは
&self
を受け取る戻り値を
Self
にしない
トレイトです。戻り値をSelf
にしないのはSelf
が本来何なのかが分からない為です。
例えば以下のA
がトレイトオブジェエクトになります。
struct Foo;
trait A {
fn get_num(&self) -> i32 {
33
}
}
impl A for Foo {}
let foo: Box = Box::new(Foo);
foo.get_num();
impl Trait 型
型としてimpl Trait
を使うと「そのトレイトを持っている値ならなんでも」入れる事ができます。以下はstr
とi32
はどちらもDisplay
を持っているのでimpl Display
に入れられます。
use std::fmt::Display;
let nju: impl Display = "nju";
let thirty_three: impl Display = 33;
assert_eq!(format!("{}{}", nju, thirty_three), "nju33")
ファットポインタ
トレイトオブジェクトはファットポインタです。これは参照な型の時に普通の方に比べて使用するメモリが2倍になっています。実際に2倍かどうかはstd::mem::size_of
で確認できます。
use std::mem::size_of;
println!("{}", size_of::<&Foo>()); // 8
println!("{}", size_of::<&A>()); // 16
&A
の場合data
(&self
に当たるもの)とvtable
と呼ばれるトレイトが持つ関数をまとめたものへのポインタの2つの参照を保持してる為です。