項目5:型変換を理解しよう
https://effective-rust.com/casts.html
LT;DR
Rust には 3 つの型変換が存在する
1. From / Into トレイトを実装して定義する型変換
変換する型には From を実装する
トレイト境界 には Into を実装する
変換に失敗する可能性がある場合、TryFrom / TryInto を実装する
2. as を用いた キャスト
データが失われる変換も可能
ユーザ定義型では、数値に関連付けた enum に対してのみ可能
3. 暗黙のうちに行われる自動型変換
多くは ポインタ と 参照型 に関するもの
ユーザ定義型では、Deref / DerefMut を実装するか、ユーザ定義型からその型が実装しているトレイトの トレイトオブジェクト へ変換する場合のみ行われる
hr.icon
Rust の 3 つの 型変換
1. 手動: From と Into トレイト を実装して定義する型変換
2. 半自動: as を用いた明示的な キャスト
3. 自動: 暗黙のうちに行われる新しい型への 自動型変換(coercion)
例外はあるが、半自動と自動は自身が定義した型にはあまり適用されない
また、他の言語とは異なり、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(!)で置き換えられる予定
また、From には reflextive implementation と呼ばれる特殊な実装が存在する
code:rs
impl<T> From<T> for T {
fn from(t: T) -> T {
t
}
}
これは、「ある型 T から、T が得られること」を表す
どのような場合に使われるか?
e.g. Newtype パターン とそれに対する関数を考える
定義
code:rs
#derive(Clone, Copy, Debug)
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 トレイト境界を組み合わせることで、呼び出し元からは暗黙的に型変換が行われているように見える
しかし、実際には明示的な変換が行われている
このパターンは、参照型 とそれに関連する変換トレイトを組み合わせるときに更に強力になる
項目8:参照型とポインタ型に慣れよう
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();
データが失われ得る変換も可能
https://internals.rust-lang.org/t/lets-deprecate-as-for-lossy-numeric-casts/16283
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 による変換を用いるようにしよう」
デフォルトでは無効になっているが、Clippy の as_conversions を有効にすると、強制することが可能
自動型変換
as を用いたキャストは、暗黙の自動型変換の スーパーセット である
すべての自動型変換は as で明示的に行うことはできるが、逆は 真 ではない
自動型変換の多くは、ポインタ と 参照型 に関するものである
e.g.
可変参照(&mut T)から不変参照(&T)への変換
これにより、&T を受け取る関数の引数に &mut T を渡すことが可能
参照から生ポイントへの変換
warning.icon ただし、生ポインタを デリファレンス すると unsafe となる
変数を キャプチャ していない クロージャ から 関数ポインタ への変換
項目2:型システムを用いて共通の挙動を表現しよう
配列 から スライス への変換
具象型からその型が実装しているトレイトの トレイトオブジェクト への変換
生存期間 が長いものから短いものへの変換(サブタイピング])
項目14:生存期間を理解しよう
ユーザ定義型に対して自動型変換が行われるは以下の 2 つのケースのみ
1. ユーザ定義型が Deref / DerefMut トレイトを実装している場合
この場合、独自型を内部で保持する型(Target)の参照へ変換できる
これにより、ユーザ定義型が スマートポインタ のように振る舞うことが可能
e.g. Box や Rc、RefCell、Arc など…
2. ユーザ定義型からその型が実装しているトレイトの トレイトオブジェクト への変換
この際、値への ファットポインタ が作成される
ファットポインタ: メモリ位置へのポインタだけでなく、具象型のトレイト実装を保持した vtable へのポインタも含むポインタ
項目8:参照型とポインタ型に慣れよう
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目