トレイト

トレイトはコードを(自分やチーム的に)分かりやすい単位で再利用する為の仕組みです。例えばあるトレイト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 ABがあるとします。

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);

もし、同じくIsCがあるならまた繰り返し…でもいいですが、ジェネリックという仕組みを使うと1回で済ませられます。

fn is_true<T: Is>(s: &T) -> bool {
  s.is_true()
}

assert_eq!(is_true(&a), true);
assert_eq!(is_true(&b), true);

関数is_trueが期待するsIsを実装済みのものに限るという意味になる為です。

ちなみにこれはコンパイル時に以下のような形になるようです。

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<dyn A> = Box::new(Foo);
foo.get_num();

impl Trait 型

型としてimpl Traitを使うと「そのトレイトを持っている値ならなんでも」入れる事ができます。以下はstri32はどちらも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つの参照を保持してる為です。