第40回です。前回はこちら。
[第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) }
- 14.9.1. テストケース: 単位を扱う
- どう言う場面で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を開催した。
幽霊型、とらえどころがない...👻
今週のプルリクエストはこちら。