第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句でちょっとよく分からなくなってきた。
今週のプルリクエストはこちら。