Rust dojo第38回を開催した

第38回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第38回の様子]

2022/05/11に第38回を開催した。

 

内容としてはRust By Example 日本語版の「14.3. ジェネリックトレイト」から「14.6. Where句」に取り組んだ。

 

参加者は5人。安定していて良い。

 

[学んだこと]

// コピー不可な型:値のコピーではなくムーブが起きる
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) {}
}
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
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
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句でちょっとよく分からなくなってきた。

 

今週のプルリクエストはこちら。

github.com