Rust dojo第40回を開催した👻

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

kdnakt.hatenablog.com

 

 

[第40回の様子]

2022/05/25に第40回を開催した👻

 

内容としてはRust By Example 日本語版の「14.9. 幽霊型パラメータ」から「14.9.1. テストケース: 単位を扱う」に取り組んだ。

 

参加者は自分を入れて2人。締切が近かったせいか、人が少なかった...悲しみ。

 

[学んだこと]

  • 14.9. 幽霊型パラメータ
  • 幽霊型(Phantom Type)は、コンパイル時に型チェックを行うためのもので、実行時には存在しない、という不思議なやつらしい
  • 実装する際はstd::marker::PhantomData型を利用して、以下のように書く
use std::marker::PhantomData;

#[derive(PartialEq)] // ==演算子を利用するためのトレイト
struct PhantomTuple<A, B>(A,PhantomData<B>);

fn main() {
    // Bにそれぞれf32,f64が割り当てられているが、PhantomDataなので実行時に値が存在しない
    let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
    let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);

    // println!("{}", _tuple1 == _tuple2);
    // この行はコンパイルエラー
}

// コンパイルエラーの詳細
error[E0308]: mismatched types
  --> src/main.rs:44:31
   |
44 |     println!("{}", _tuple1 == _tuple2);
   |                               ^^^^^^^ expected `f32`, found `f64`
   |
   = note: expected struct `PhantomTuple<_, f32>`
              found struct `PhantomTuple<_, f64>`
  • structの場合も同様に実装できる
use std::marker::PhantomData;

#[derive(PartialEq,Debug)]
struct PhantomStruct<A, B> {
    first: A,
    phantom: PhantomData<B>
}

fn main() {
    let _struct1: PhantomStruct<char, f32> = PhantomStruct {
        first: 'Q',
        phantom: PhantomData,
    };
    
    println!("{:?}", _struct1);
    // 実行時には値や型情報がなくなるため、以下のように出力される
    // PhantomStruct { first: 'Q', phantom: PhantomData }
}
  • 本文中ではタプルとstructだけだったので、enumでもPhantomDataを利用してみた
use std::marker::PhantomData;

#[derive(Debug)]
enum Event<A> {
    KeyPress(PhantomData<A>),
}

fn main() {
    let event: Event<f64> = Event::KeyPress(PhantomData);
    println!("{:?}", event); // KeyPress(PhantomData)
} 
use std::ops::Add;
use std::marker::PhantomData;

#[derive(Debug, Clone, Copy)]
enum Inch {}
#[derive(Debug, Clone, Copy)]
enum Mm {}


#[derive(Debug, Clone, Copy)]
struct Length<Unit>(f64, PhantomData<Unit>); // Unitが幽霊型

/// `Add`トレイトは+演算子の動作を定義する。
impl<Unit> Add for Length<Unit> {
     type Output = Length<Unit>; // 前回出てきた関連型

    fn add(self, rhs: Length<Unit>) -> Length<Unit> {
        Length(self.0 + rhs.0, PhantomData)
    }
}

fn main() {
    let one_foot:  Length<Inch> = Length(12.0, PhantomData);
    
    let one_meter: Length<Mm>   = Length(1000.0, PhantomData);

    let two_feet = one_foot + one_foot;
    println!("one foot + one_foot = {:?} in", two_feet.0); // 24.0 in

    let two_meters = one_meter + one_meter;
    println!("one meter + one_meter = {:?} mm", two_meters.0); // 2000.0 mm

    //型が違うのでコンパイルエラー
    //let one_feter = one_foot + one_meter;
}

// コンパイルエラーの詳細
error[E0308]: mismatched types
  --> src/main.rs:63:32
   |
63 |     let one_feter = one_foot + one_meter;
   |                                ^^^^^^^^^ expected enum `Inch`, found enum `Mm`
   |
   = note: expected struct `Length<Inch>`
              found struct `Length<Mm>`
  • ただ、やはり長さの情報はprintlnで使うときなどに知りたい気もするし、値として利用したいシーンの方が多い気もするので、幽霊型の便利さが今ひとつ理解できなかった。

 

[まとめ]

モブプログラミングスタイルでRust dojoを開催した。

幽霊型、とらえどころがない...👻

 

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

github.com