第38回です。前回はこちら。
[第38回の様子]
2022/05/11に第38回を開催した。
内容としてはRust By Example 日本語版の「14.3. ジェネリックトレイト」から「14.6. Where句」に取り組んだ。
参加者は5人。安定していて良い。
[学んだこと]
- 14.3. ジェネリックトレイト
- トレイトについてもジェネリックスを活用できる
// コピー不可な型:値のコピーではなくムーブが起きる struct Empty; struct Null; // ジェネリック型 `T`に対するトレイト trait DoubleDrop<T> { fn double_drop(self, _: T); } // `U`を`self`として、`T`をもう一つの引数として受け取る`DoubleDrop<T>` impl<T, U> DoubleDrop<T> for U { // このメソッドは2つの引数の所有権を取り、メモリ上から開放する。 fn double_drop(self, _: T) {} } fn main() { let empty = Empty; let null = Null; // `empty`と`null`を開放 empty.double_drop(null); //empty; //null; // ^ これらの行をアンコメントすると、以下のコンパイルエラー // error[E0382]: use of moved value: `empty` // --> src/main.rs:36:5 }
- 具象型(この場合はEmpty)に直接DoubleDropトレイトを実装する場合は以下のようになる
impl<T> DoubleDrop<T> for Empty { fn double_drop(self, _: T) {} }
- 14.4. ジェネリック境界
- ジェネリックな型パラメータに特定の機能を要求する場合は境界を指定する
- 以下の例では、T型にDisplayトレイトの実装を要求する
fn printer<T: Display>(t: T) { println!("{}", t); }
- トレイトによって境界が定められるので、ジェネリクスがトレイトの関数にアクセスできる
trait HasArea { fn area(&self) -> f64; } impl HasArea for Rectangle { fn area(&self) -> f64 { self.length * self.height } } struct Rectangle { length: f64, height: f64 } fn area<T: HasArea>(t: &T) -> f64 { t.area() } fn main() { let rectangle = Rectangle { length: 3.0, height: 4.0 }; println!("Area: {}", area(&rectangle)); // Area: 12と出力される }
- 境界が満たされない場合は以下のようにコンパイルエラーとなる
trait HasArea { fn area(&self) -> f64; } struct Triangle { length: f64, height: f64 } fn main() { let _triangle = Triangle { length: 3.0, height: 4.0 }; println!("Area: {}", area(&_triangle)); } // 以下のエラー error[E0277]: the trait bound `Triangle: HasArea` is not satisfied --> src/main.rs:40:31
- 14.4.1. テストケース: 空トレイト
- 境界として利用するトレイトは必ずしも機能を必要としない
struct Cardinal; struct BlueJay; // 空のトレイト trait Red {} trait Blue {} impl Red for Cardinal {} impl Blue for BlueJay {} fn red<T: Red>(_: &T) -> &'static str { "red" } fn blue<T: Blue>(_: &T) -> &'static str { "blue" } fn main() { let cardinal = Cardinal; let blue_jay = BlueJay; println!("A cardinal is {}", red(&cardinal)); println!("A blue jay is {}", blue(&blue_jay)); }
// 空のトレイト trait Red {} trait Blue {} // 同名の別関数を定義しようとするが... fn color<T: Red>(_: &T) -> &'static str { "red" } fn color<T: Blue>(_: &T) -> &'static str { "blue" } // 以下のコンパイルエラー error[E0428]: the name `color` is defined multiple times --> src/main.rs:16:1
- 14.5. 複数のジェネリック境界
- 1つのジェネリクス型に複数の境界を設ける場合の書き方は
+
を利用する
fn compare_prints<T: Debug + Display>(t: &T) { println!("Debug: `{:?}`", t); println!("Display: `{}`", t); }
- 複数のジェネリクス型にそれぞれ境界を設けることもできる
fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) { println!("t: `{:?}`", t); println!("u: `{:?}`", u); }
- 14.6. Where句
- 複数の境界を設ける場合は、Where句を利用した方が可読性が高い
// ジェネリクスを含むMyTraitトレイトをYourType型に実装する impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {} // 同じことをWere句を使って表現する impl <A, D> MyTrait<A, D> for YourType where A: TraitB + TraitC, D: TraitE + TraitF {}
- Where句を使わないと表現できないケースもある
use std::fmt::Debug; trait PrintInOption { fn print_in_option(self); } impl<T> PrintInOption for T where Option<T>: Debug { // プリントされるのが`Some(self)`であるため、この関数の // ジェネリック境界として`Option<T>: Debug`を使用したい。 fn print_in_option(self) { println!("{:?}", Some(self)); } } fn main() { let vec = vec![1, 2, 3]; vec.print_in_option(); }
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
ジェネリクス少しわかってきたけど、最後のWhere句でちょっとよく分からなくなってきた。
今週のプルリクエストはこちら。