境界
ジェネリックを扱う際、型パラメータは、型がどの機能を実装しているかを規定するために、しばしばトレイトを_境界_として使用しなければなりません。たとえば、以下の例では出力するためにトレイト Display を使用しているため、T は Display によって境界付けられている必要があります。つまり、T は Display を実装_しなければなりません_。
// トレイト `Display` を実装しなければならないジェネリック型 `T` を
// 受け取る関数 `printer` を定義する。
fn printer<T: Display>(t: T) {
println!("{}", t);
}
境界を設けると、ジェネリックはその境界に準拠する型に制限されます。つまり:
struct S<T: Display>(T);
// エラー! `Vec<T>` は `Display` を実装していない。この
// 特殊化は失敗する。
let s = S(vec![1]);
境界のもう1つの効果は、ジェネリックなインスタンスが、境界で指定されたトレイトのメソッドにアクセスできるようになることです。たとえば:
// プリントマーカー `{:?}` を実装するトレイト。 use std::fmt::Debug; trait HasArea { fn area(&self) -> f64; } impl HasArea for Rectangle { fn area(&self) -> f64 { self.length * self.height } } #[derive(Debug)] struct Rectangle { length: f64, height: f64 } #[allow(dead_code)] struct Triangle { length: f64, height: f64 } // ジェネリック `T` は `Debug` を実装しなければならない。型に // 関係なく、これは正しく動作する。 fn print_debug<T: Debug>(t: &T) { println!("{:?}", t); } // `T` は `HasArea` を実装しなければならない。境界を満たす // 任意の型は `HasArea` の関数 `area` にアクセスできる。 fn area<T: HasArea>(t: &T) -> f64 { t.area() } fn main() { let rectangle = Rectangle { length: 3.0, height: 4.0 }; let _triangle = Triangle { length: 3.0, height: 4.0 }; print_debug(&rectangle); println!("面積: {}", area(&rectangle)); //print_debug(&_triangle); //println!("面積: {}", area(&_triangle)); // ^ TODO: これらのコメントを外してみよう。 // | エラー: `Debug` と `HasArea` のどちらも実装していない。 }
追加の注記として、where 句も、場合によっては境界を適用するために使用でき、より表現しやすくできます。