導入
レッスン36で「T の値を比較したいけど、どんな型でも比較できるとは限らない」という問題が残りました。トレイトを使えば「T は○○できる型に限る」と条件を付けられます。これが トレイト境界(trait bound) で、ジェネリクスとトレイトが合流する要の部分です。
説明
「要約できる型なら何でも受け取る」関数を書きます。
trait Summary {
fn summarize(&self) -> String;
}
// T は「Summary を実装している型」に限る、という境界を付ける
fn notify<T: Summary>(item: &T) {
println!("速報! {}", item.summarize());
}
// impl Trait 構文(同じ意味の短い書き方)
fn notify2(item: &impl Summary) {
println!("速報! {}", item.summarize());
}
<T: Summary>… 「型TはSummaryトレイトを実装していること」という条件(境界)。この関数には、summarize()できる型しか渡せません。- 逆に言うと、
itemがSummaryを実装していることが保証されるので、関数の中で安心してitem.summarize()を呼べます。 &impl Summary… 同じ意味の短縮記法。
複数の条件は + で足せます。標準的な例を見ましょう。
use std::fmt::Display;
// T は「比較できて(PartialOrd)、表示できる(Display)」型に限る
fn largest<T: PartialOrd + Display>(list: &[T]) -> &T {
let mut biggest = &list[0];
for item in list {
if item > biggest { // PartialOrd があるので > で比較できる
biggest = item;
}
}
biggest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("最大は {}", largest(&numbers)); // 100
let chars = vec!['y', 'm', 'a', 'q'];
println!("最大は {}", largest(&chars)); // y
}
T: PartialOrd + Display… 「比較でき、かつ表示できる型」。この境界があるおかげで、関数内で>(比較)と{}(表示)が使えます。largestはi32のベクタにもcharのベクタにも使えます。1つのコードで、条件を満たすあらゆる型に対応。
トレイト境界は「ジェネリクスの自由さ」と「型の安全性」を橋渡しします。「どんな型でもいい、ただし○○できるなら」——この絞り込みで、汎用的なのに安全なコードが書けるのです。これが Rust の抽象化の到達点であり、標準ライブラリの Iterator や From/Into など、あらゆる場所でこの仕組みが使われています。
試すには
largest からトレイト境界 PartialOrd を外すと、item > biggest の比較が「その型が比較できるか分からない」とエラーになります。境界が「関数内で使える操作」を保証していることが分かります。「汎用にしたいが、この能力は必要」という設計を、境界で表現してみましょう。