型間のキャスト

Rustは安全性に焦点を合わせており、異なる型の間を互いにキャストするために二つの異なる方法を提供しています。 一つは as であり、これは安全なキャストに使われます。 逆に transmute は任意のキャストに使え、Rustにおける最も危険なフィーチャの一つです!

型強制

型強制は暗黙に行われ、それ自体に構文はありませんが、as で書くこともできます。

型強制が現れるのは、 letconststatic 文、関数呼び出しの引数、構造体初期化の際のフィールド値、そして関数の結果です。

一番よくある型強制は、参照からミュータビリティを取り除くものです。

似たような変換としては、 生ポインタ からミュータビリティを取り除くものがあります。

参照も同様に、生ポインタへ型強制できます。

Deref によって、カスタマイズされた型強制が定義されることもあります。

型強制は推移的です。

as

as というキーワードは安全なキャストを行います。

fn main() { let x: i32 = 5; let y = x as i64; }
let x: i32 = 5;

let y = x as i64;

安全なキャストは大きく三つに分類されます。 明示的型強制、数値型間のキャスト、そして、ポインタキャストです。

キャストは推移的ではありません。 e as U1 as U2 が正しい式であったとしても、 e as U2 が必ずしも正しいとは限らないのです。 (実際、この式が正しくなるのは、 U1U2 へ型強制されるときのみです。)

明示的型強制

e as U というキャストは、 e が型 T を持ち、かつ TU型強制 されるとき、有効です。

数値キャスト

e as U というキャストは、以下のどの場合でも有効です。

例えば、

fn main() { let one = true as u8; let at_sign = 64 as char; let two_hundred = -56i8 as u8; }
let one = true as u8;
let at_sign = 64 as char;
let two_hundred = -56i8 as u8;

数値キャストのセマンティクスは以下の通りです。

ポインタキャスト

驚くかもしれませんが、いくつかの制約のもとで、 生ポインタ と整数の間のキャストや、ポインタと他の型の間のキャストは安全です。 安全でないのはポインタの参照外しだけなのです。

fn main() { // let a = 300 as *const char; // a pointer to location 300 let a = 300 as *const char; // 300番地へのポインタ let b = a as u32; }
let a = 300 as *const char; // 300番地へのポインタ
let b = a as u32;

e as U が正しいポインタキャストであるのは、以下の場合です。

transmute

as は安全なキャストしか許さず、例えば4つのバイト値を u32 へキャストすることはできません。

fn main() { let a = [0u8, 0u8, 0u8, 0u8]; // let b = a as u32; // four eights makes 32 let b = a as u32; // 4つの8で32になる }
let a = [0u8, 0u8, 0u8, 0u8];

let b = a as u32; // 4つの8で32になる

これは以下のようなメッセージがでて、エラーになります。

error: non-scalar cast: `[u8; 4]` as `u32`
let b = a as u32; // 4つの8で32になる
        ^~~~~~~~

これは「non-scalar cast」であり、複数の値、つまり配列の4つの要素、があることが原因です。 この種類のキャストはとても危険です。 なぜなら、複数の裏に隠れた構造がどう実装されているかについて仮定をおいているからです。 そのためもっと危険なものが必要になります。

transmute 関数は コンパイラ intrinsic によって提供されており、やることはとてもシンプルながら、とても恐ろしいです。 この関数は、Rustに対し、ある型の値を他の型であるかのように扱うように伝えます。 これは型検査システムに関係なく行われ、完全に使用者頼みです。

先ほどの例では、4つの u8 からなる配列が ちゃんと u32 を表していることを知った上で、キャストを行おうとしました。 これは、as の代わりに transmute を使うことで、次のように書けます。

fn main() { use std::mem; unsafe { let a = [0u8, 0u8, 0u8, 0u8]; let b = mem::transmute::<[u8; 4], u32>(a); } }
use std::mem;

unsafe {
    let a = [0u8, 0u8, 0u8, 0u8];

    let b = mem::transmute::<[u8; 4], u32>(a);
}

コンパイルを成功させるために、この操作は unsafe ブロックでくるんであります。 技術的には、 mem::transmute の呼び出しのみをブロックに入れればいいのですが、今回はどこを見ればよいかわかるよう、関連するもの全部を囲んでいます。 この例では a に関する詳細も重要であるため、ブロックにいれてあります。 ただ、文脈が離れすぎているときは、こう書かないこともあるでしょう。 そういうときは、コード全体を unsafe でくるむことは良い考えではないのです。

transmute はほとんどチェックを行わないのですが、最低限、型同士が同じサイズかの確認はします。 そのため、次の例はエラーになります。

fn main() { use std::mem; unsafe { let a = [0u8, 0u8, 0u8, 0u8]; let b = mem::transmute::<[u8; 4], u64>(a); } }
use std::mem;

unsafe {
    let a = [0u8, 0u8, 0u8, 0u8];

    let b = mem::transmute::<[u8; 4], u64>(a);
}

エラーメッセージはこうです。

error: transmute called with differently sized types: [u8; 4] (32 bits) to u64
(64 bits)

ただそれ以外に関しては、自己責任です!