項目5:型変換を理解しよう
LT;DR
Rust には 3 つの型変換が存在する
1. From / Into トレイトを実装して定義する型変換
変換する型には From を実装する
変換に失敗する可能性がある場合、TryFrom / TryInto を実装する
データが失われる変換も可能
ユーザ定義型では、数値に関連付けた enum に対してのみ可能
3. 暗黙のうちに行われる自動型変換
ユーザ定義型では、Deref / DerefMut を実装するか、ユーザ定義型からその型が実装しているトレイトの トレイトオブジェクト へ変換する場合のみ行われる hr.icon
1. 手動: From と Into トレイト を実装して定義する型変換 例外はあるが、半自動と自動は自身が定義した型にはあまり適用されない
また、他の言語とは異なり、Rust の数値型では自動的な型変換を行わない そのため、以下はコンパイルエラーとなる
code:rs
let x: u32 = 2;
let y: u64 = x;
手動型変換(ユーザ定義型変換)
From<T>: 実装した型の値は、型 T の値から変換することができ、この変換は常に成功する
TryFrom<T>: 実装した型の値は、型 T の値から変換することができるが、この変換は失敗する可能性がある
Into<T>: 実装した型の値は、型 T の値へ変換することができ、この変換は常に成功する
TryInto<T>: 実装した型の値は、型 T の値へ変換することができるが、この変換は失敗する可能性がある
Try* は変換する結果の型が Result になっている
また、 定義時にエラー型 E を指定する必要がある
「変換が失敗する可能性がある場合には、Try... だけを実装しよう」
From と Try のトレイトには 対称性 がある つまり、型 T を Into<U> の into で型 U に変換できるなら、型 U は From<T> の from を用いて型 T から作れるはずである
実際、From トレイトを実装すると自動的に Into トレイトも実装される(ブランケット実装) code:rs
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
Rust ではこのような対象性がある場合、2 つの選択肢のうち 1 つを選ぶ必要がある(トレイト整合性ルール) そのため、
「変換には From トレイトを実装しよう」
「トレイト制約には Into を使おう」
これにより、Into を直接実装したものも From だけを直接実装したものも受け付けられる
標準ライブラリの型に対しては、上記のトレイトの様々な実装がされている
e.g. 整数型
すべての値が変換可能な型は From が、失敗する可能性がある型は TryFrom が実装されている
e.g. From<u32> for u64, TryFrom<u64> for u32
Into 以外の標準ライブラリのブランケット実装
この実装では、対象となるデータ型のインスタンスから、自動的にスマートポインタを生成する
したがって、スマートポインタパラメータを受け取る ジェネリックメソッド は、従来の通常の型も受け取ることが可能である 他にも、TryFrom にも Into トレイトを実装した型に対する、逆向きのブランケット実装が存在する
code:rs
impl<T, U> TryFrom<U> for T
where
U: Into<T>,
{
type Error = Infallible;
fn try_from(value: U) -> Result<Self, Self::Error> {
Ok(U::into(value))
}
}
これは、「ある型 T が失敗することなく U に変換できるなら、失敗する可能性ありで T から U を得られる」ことを表している
この変換は常に成功するので、エラー型は Infalliable(絶対)となっている
将来的には never(!)で置き換えられる予定
code:rs
impl<T> From<T> for T {
fn from(t: T) -> T {
t
}
}
これは、「ある型 T から、T が得られること」を表す
どのような場合に使われるか?
定義
code:rs
pub struct IanaAllocated(pub u64);
impl From<u64> for IanaAllocated {
fn from(v: u64) -> Self {
Self(v)
}
}
pub fn is_iana_reserved(s: IanaAllocated) -> bool {
s.0 == 0 || s.0 == 65535
}
呼び出し元
code:rs
let s = IanaAllocated(1);
println!("{:?} reserved? {}", s, is_iana_reserved(s));
この定義では、is_iana_reserved は、u64 の値に対して呼び出すことは不可能
code:rs
if is_iana_reserved(42) {
// ...
}
しかし、Into<IanaAllocated> をトレイト境界とするジェネリック関数に再定義すると、コンパイルが通る
code:rs
pub fn is_iana_reserved<T>(s: T) -> bool
where
T: Into<IanaAllocated>,
{
let s = s.into();
s.0 == 0 || s.0 == 65535
}
このとき、 reflextive implementation により、元々 IanaAllocated の値に対しては変換することなく動作できるようになっている
上記のサンプルコードから分かるように、Rust では From と Into トレイト境界を組み合わせることで、呼び出し元からは暗黙的に型変換が行われているように見える
しかし、実際には明示的な変換が行われている
このパターンは、参照型 とそれに関連する変換トレイトを組み合わせるときに更に強力になる e.g. AsRef / AsMut や Deref / DerefMut、Borrow / BorrowMut などの ポインタトレイト 半自動的型変換(キャスト)
限定的 ではあるが、as を用いることでキャストすることが可能
e.g. 整数型
code:rs
let x: u32 = 9;
let y = x as u64;
let z: u64 = x.into();
データが失われ得る変換も可能
code:rs
let x: u32 = 9;
let y = x as u16;
ユーザ定義型では、整数値に関連付けた enum に限定される
code:rs
enum MyEnum {
A = 1,
B,
C,
}
let value: u8 = MyEnum::B as u8;
println!("The value of MyEnum::B is: {}", value);
キャストの セマンティクス を理解した上で、それが必要だと判断した場合(e.g. C との互換性)以外は、一貫性と安全性のために 「as によるキャストではなく、from / into による変換を用いるようにしよう」
自動型変換
すべての自動型変換は as で明示的に行うことはできるが、逆は 真 ではない e.g.
可変参照(&mut T)から不変参照(&T)への変換
これにより、&T を受け取る関数の引数に &mut T を渡すことが可能
参照から生ポイントへの変換
ユーザ定義型に対して自動型変換が行われるは以下の 2 つのケースのみ
1. ユーザ定義型が Deref / DerefMut トレイトを実装している場合
この場合、独自型を内部で保持する型(Target)の参照へ変換できる
e.g. Box や Rc、RefCell、Arc など…
ファットポインタ: メモリ位置へのポインタだけでなく、具象型のトレイト実装を保持した vtable へのポインタも含むポインタ