境界
ジェネリックプログラミングをしていると、型パラメータが特定の機能を持っていることを規定するため、トレイトに境界を設ける必要があることがよくあります。例えば、以下の例では、引数の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]);
境界のもう一つの効果は、ジェネリック型のインスタンスが、境界条件となるトレイトのメソッドにアクセスすることができるようになる点です。以下がその例です。
// 出力時のマーカー`{:?}`を実装するトレイト 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`トレイトを実装していなくてはなりません。 // その限りにおいて、`T`がどのような具象型であろうとも次の関数は動作します。 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: {}", area(&rectangle)); //print_debug(&_triangle); //println!("Area: {}", area(&_triangle)); // ^ TODO: これらの行をアンコメントしてみましょう。 // | Error: `Debug` も `HasArea`もどちらも実装されていません! }
付け加えておくと、where
句を用いて境界を適用することもできます。場合によってはこちらの記法を使用したほうが読みやすくなる場合もあります。