TL;DR
- クロージャを受け取るコードでは、使える範囲で最も一般的な
Fn*トレイトを使う。関数ポインタよりFn*のトレイト境界のほうがよい。 - 関数シグネチャで表せない挙動の違いは、マーカトレイトで表す。ジェネリックなコードで型に求める要件は、トレイト境界で表す。
- トレイトオブジェクトは実装を実行時に決める(動的ディスパッチ)。
内容
https://effective-rust.com/use-types-2.html
関数とメソッド
関数は、一連のコードに名前とパラメータリストを関連付ける仕組みである。メソッドは、特定のデータ構造のインスタンスに関連付けられた関数で、impl ブロックに書く。最初の引数 self の型は、内容を読むだけなら &self、変更するなら &mut self、データ構造を消費するなら self にする。メソッドは struct だけでなく enum にも定義できる。
関数ポインタ
関数ポインタは、実行するコード自体を実行時に変えられるようにする抽象化である。関数のシグネチャを表す fn 型を持つ。型はコンパイル時に検査されるので、実行時のサイズはただのポインタと同じである。fn 型は Copy、Eq、std::fmt::Pointer を実装しており、普通の値として扱える。
fn sum(x: i32, y: i32) -> i32 { x + y }
let op: fn(i32, i32) -> i32 = sum;ただし、関数名を変数に入れただけでは関数ポインタにならず、fn 型への明示的な型変換が要る。sum そのものの型は、関数のシグネチャと位置をエンコードした、コンパイラ内部の型である。
クロージャ
関数ポインタが使える入力は、明示的に渡す引数だけである。外部の状態に依存する処理を渡したいときはクロージャを使う。クロージャは式の一部として名前なしで書け、引数の型は多くの場合コンパイラが推論する。さらに、周りの環境の値をキャプチャできる。
let amount_to_add = 3;
let add_n = |y| y + amount_to_add;
assert_eq!(add_n(5), 8);クロージャを作ると、キャプチャした値を持つ一時的な型のインスタンスが作られ、実行時にコンテキストとして渡される。保持するのは多くの場合、環境の値への不変参照である(項目8)。可変参照を持つことも、move で値を移すこともできる。
関数ポインタを受け取る関数にクロージャは渡せない。クロージャを受け取るには、次のどれかの Fn* トレイトを使う。コンパイラは、クロージャの中身に応じて適切なトレイトを自動で実装する。
FnOnce: 1 回だけ呼べる。キャプチャした値を移動するFnMut: 繰り返し呼べる。環境を可変参照で持ち、変更できるFn: 繰り返し呼べる。環境を共有参照で持つ
Fn は FnMut を、FnMut は FnOnce を前提とする。そのため、FnOnce を求めるコードに FnMut のクロージャを、FnMut を求めるコードに Fn のクロージャを渡せる。unsafe でない関数ポインタ型は環境から何も借用しないので、すべての Fn* トレイトを実装する。
トレイト
トレイトは、型が提供する関数やメソッドの集まりを定義する。Go や Java のインタフェース、C++ の抽象クラスに当たる。メソッドの名前は、コンパイラが同じシグネチャの関数を区別するラベルになり、プログラマが意図を推測する手がかりにもなる。トレイトはデフォルト実装を持てる。将来の柔軟性が心配なら、具象型ではなくトレイトを受け取るようにする。
マーカトレイト
マーカトレイトは、メソッドを持たず、型システム上の区別だけに使うトレイトである。たとえばソートが安定かどうかは sort メソッドの引数では表せないので、StableSort というマーカトレイトで表す。実装する側は明示的に宣言する必要があり、その性質を保証する責任を負う。
pub trait Sort {
fn sort(&mut self);
}
pub trait StableSort: Sort {}トレイト境界
トレイト境界は、ジェネリックなコードが扱う型 T に、特定のトレイトの実装を要求するものである。T がトレイトの関数を持つことはコンパイル時に保証される。この検査は、ジェネリクスのコードを具体的な型のコードに変換する単相化のときに行われる。トレイト境界がなければ、T に対しては移動やドロップくらいしかできない。
pub fn dump_sorted<T>(mut collection: T)
where
T: Sort + IntoIterator,
T::Item: std::fmt::Debug,
{
collection.sort();
for item in collection {
println!("{:?}", item);
}
}トレイトオブジェクト
トレイトオブジェクトは、使うトレイトの実装を実行時に決める。サイズがコンパイル時に分からないので、&dyn Trait や Box<dyn Trait> のように参照やポインタを介して扱う。トレイトオブジェクトにするには、すべてのメソッドが次の条件(トレイトオブジェクト安全性)を満たす必要がある。
Selfのサイズが分からないので、戻り値や、レシーバ以外の引数の型をSelfにしない- トレイト境界なら実際に使われた型の分だけ実装すればよいが、トレイトオブジェクトは実行時に現れうるすべての型にコンパイル時に対応しなければならないので、ジェネリック関数を持たない
参考: https://doc.rust-jp.rs/book-ja/ch17-02-trait-objects.html