足を止めて見る #2 〜 RustのFromトレイト 〜
足を止めて見よう
足を止めて見ようシリーズの2つ目です。
前回は Display トレイトについてでした。
std::convert::From トレイトを実装しよう
ある型に対して、別の型から変換してその型を作る方法を定義できるのが std::convert::From トレイトですよね。
今回は足を止めて std::convert::From トレイトを実装してみます。
std::convert::From トレイトを実装する構造体を用意してみる
下準備として、Person という構造体を用意してみます。
構造体には、nickname と age フィールドを持っています。
ageフィールドは、プリミティブ型ではなくAge型を取るようにしてみます。
#[derive(Debug)]
struct Person {
nickname: String,
age: Age,
}
#[derive(Debug)]
struct Age(u8);
fn main() {
let age_value = 35_u8;
let yoshida = Person {
nickname: String::from("yosshi-"),
age: Age::from(age_value), // mismatched types (expected Age, found u8)
};
println!("{:?}", yoshida);
}
Ageはいわゆるユニット様構造体という型です。
無理やり実行してみると mismatched types エラーになってしまいます(当然ですね)。
age_value が expected Age, found u8 とエラーメッセージが出ているので、 Age型 は Age型 からの変換しか知らないよ、ということでしょう。
では std::convert::From トレイトを利用して、u8から変換できるようにしてみます。
std::convert::From トレイトを実装してみる
Age 構造体に std::convert::From トレイトを実装してみます。
#[derive(Debug)]
struct Person {
nickname: String,
age: Age,
}
#[derive(Debug)]
struct Age(u8);
impl From<u8> for Age {
fn from(value: u8) -> Self {
Age(value)
}
}
fn main() {
let age_value = 35_u8;
let yoshida = Person {
nickname: String::from("yosshi-"),
age: Age::from(age_value),
};
println!("{:?}", yoshida);
}
impl From<u8> for Age を書いてみました。u8であればそのまま、ユニット様構造体のAge型に当てはめられるのですんなりです。
実行してみると、期待通りエラーにならずデバッグ出力されます。
Person { nickname: "yosshi-", age: Age(35) }
もう一段だけ深ぼってみる #1
なんで impl std::convert::From<u8> for Age と書かなくて済んでいるんでしょうか?
これは自分知っていました。答えは、Rustのprelude(プレリュード)というシステムのおかげですよね。
全てのRustプログラムで自動的にインポートされるモジュールがいくつか存在していて std::convert::From もその一つということです。
以下のページでプレリュードに含まれるモジュールを確認できます。std::convert::From は最初期からプレリュードに含まれているようですね。
Rustの成長とともに、プレリュードに含まれるモジュールも増えていきそうですね。
もう一段だけ深ぼってみる #2
std::convert::From トレイトを実装すると into メソッドが利用できると聞いたことがあります。ちょっと試してみましょう。
#[derive(Debug)]
struct Person {
nickname: String,
age: Age,
}
#[derive(Debug)]
struct Age(u8);
impl From<u8> for Age {
fn from(value: u8) -> Self {
Age(value)
}
}
fn main() {
let age_value = 35_u8;
let yoshida = Person {
nickname: String::from("yosshi-"),
age: age_value.into(), // 変更前: age: Age::from(age_value),
};
println!("{:?}", yoshida);
}
1箇所だけ変更し age: age_value.into() に書き換えてみましたが、問題なく実行できました。噂は本当のようです。
逆に std::convert::Into トレイトを実装してみたらどうなるのでしょうか?
#[derive(Debug)]
struct Person {
nickname: String,
age: Age,
}
#[derive(Debug)]
struct Age(u8);
// 変更前: From<u8> for Age { ...
impl Into<Age> for u8 {
fn into(self) -> Age {
Age(self)
}
}
fn main() {
let age_value = 35_u8;
let yoshida = Person {
nickname: String::from("yosshi-"),
age: age_value.into(),
};
println!("{:?}", yoshida);
}
書いてみると Into と From は逆方向の関係性であることがわかりますね。
そしてこれも問題なく動作しました。
ちょっと公式のソースコードも覗いてみます。
// From implies Into
#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> Into<U> for T
where
U: From<T>,
{
/// Calls `U::from(self)`.
///
/// That is, this conversion is whatever the implementation of
/// <code>[From]<T> for U</code> chooses to do.
#[inline]
#[track_caller]
fn into(self) -> U {
U::from(self)
}
}
今回のケースに置き換えると、Tはu8、UはAge型になりそうです。
つまり From<u8> を実装しているAge型には、自動的に impl Into<Age> for u8 と実装されることが見てとれます。
そのため into メソッドが自動的に使えるようになっているようですね。
今回も、足を止めて見た甲斐がありました。
振り返り
今回も、Rustの convert/mod.rs まで踏み込んで見てみることができました。やっぱり読めるもんですね、自信になります。
これで明日から、もっと堂々と from や into を使っていけるぞー 🙌
その他
今回書いたRustのコードはこのリポジトリで制作しています。
Discussion