TL;DR

  • Rust の型変換には、From / Into で定義する変換、as によるキャスト、暗黙の型強制(coercion)の 3 種類がある。
  • 変換には From を実装し、トレイト境界には Into を使う。失敗しうる変換には TryFrom / TryInto だけを実装する。
  • as はデータが失われる変換もできるので、C との互換性などが必要な場合を除き、from / into を使う。

内容

https://effective-rust.com/casts.html

Rust の型変換は次の 3 種類に分けられる。

  1. 手動: From や Into などのトレイトを実装して定義する変換
  2. 半自動: as を使った明示的なキャスト
  3. 自動: 暗黙のうちに行われる型強制

例外はあるが、半自動と自動は自分で定義した型にはあまり適用されない。また、他の言語と違い、Rust は数値型を自動で変換しない。u32 の値を u64 の変数に代入するとコンパイルエラーになる。

ユーザ定義型の変換

型どうしの変換は、次のジェネリックなトレイトで表す。

  • From<T>: T の値から変換できる。常に成功する
  • TryFrom<T>: T の値から変換できるが、失敗しうる
  • Into<T>: T の値へ変換できる。常に成功する
  • TryInto<T>: T の値へ変換できるが、失敗しうる

Try の付くトレイトは結果が Result になり、エラー型を指定する必要がある。変換が失敗しうるなら Try の付くトレイトだけを実装する。

From と Into は対になっていて、From を実装するとブランケット実装によって Into も自動で実装される。そのため、変換には From を実装する。逆に、自分で書くジェネリック関数のトレイト境界には Into を使う。こうすると、Into を直接実装した型も From だけを実装した型も受け付けられる。

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

標準ライブラリにはこれらの実装が多くある。整数型なら、すべての値を変換できる組には From<u32> for u64 が、失敗しうる組には TryFrom<u64> for u32 が実装されている。Into 以外のブランケット実装の多くはスマートポインタ向けで、データ型のインスタンスからスマートポインタを自動で作る。また、Into を実装した型には TryFrom も実装される。この変換は常に成功するので、エラー型は Infallible である。

From には、T から T を得る impl<T> From<T> for T という反射的な実装(reflexive implementation)もある。これは、Newtype パターンの型を受け取る関数を、Into をトレイト境界とするジェネリック関数にしたときに役立つ。

pub fn is_iana_reserved<T>(s: T) -> bool
where
    T: Into<IanaAllocated>,
{
    let s = s.into();
    s.0 == 0 || s.0 == 65535
}

こうすると u64 の値でも呼び出せ、IanaAllocated の値は反射的な実装により変換なしで動く。呼び出し側からは暗黙に変換されたように見えるが、実際には明示的な変換が行われている。このパターンは、AsRef、Deref、Borrow などのポインタ関連のトレイトと組み合わせるとさらに強力になる(項目8)。

キャスト

as を使うと、限られた範囲でキャストできる。データが失われる変換(u32 から u16 など)もできてしまう。ユーザ定義型では、整数値を割り当てた enum だけが対象になる。

enum MyEnum {
    A = 1,
    B,
    C,
}
 
let value: u8 = MyEnum::B as u8;

キャストの意味を理解したうえで、C との互換性などのために必要な場合を除き、一貫性と安全性のために as ではなく from / into を使う。Clippy の as_conversions を有効にすると、これを強制できる。

型強制

as によるキャストは、暗黙の型強制を包含する。型強制はすべて as で明示的にも書けるが、逆は成り立たない。型強制の多くはポインタと参照に関するもので、たとえば次のものがある。

  • 可変参照 &mut T から不変参照 &T への変換
  • 参照から生ポインタへの変換。生ポインタの参照外しは unsafe になる
  • 変数をキャプチャしないクロージャから関数ポインタへの変換(項目2)
  • 配列からスライスへの変換
  • 具象型から、その型が実装するトレイトのトレイトオブジェクトへの変換
  • 生存期間の長い参照から短い参照への変換(項目14)

ユーザ定義型で型強制が起きるのは 2 つの場合だけである。1 つは Deref / DerefMut を実装している場合で、内部に持つ Target 型の参照へ変換できる。これにより Box や Rc のように、スマートポインタとして振る舞える。もう 1 つは、実装しているトレイトのトレイトオブジェクトへ変換する場合である。このとき、値へのポインタに加えて、トレイト実装の vtable へのポインタを持つファットポインタが作られる。

キーワード