Rust,和它对安全的关注,提供了两种不同的在不同类型间转换的方式。第一个,as
,用于安全转换。相反,transmute
允许任意的转换,而这是 Rust 中最危险的功能之一!
类型间的强制转换是隐式的并没有自己的语法,不过可以写作as
。
强转出现在let
,const
和static
语句;函数调用参数;结构体初始化的字符值;和函数返回值中。
最常用的强转的例子是从引用中去掉可变性:
&mut T
到&T
一个相似的转换时去掉一个[裸指针](Raw Pointers 裸指针.md)的可变性:
*mut T
到*const T
引用也能被强转为裸指针:
&T
到*const T
&mut T
到*mut T
自定义强转可以用[Deref
](Deref
coercions Deref
强制多态.md)定义。
强转是可传递的。
as
as
关键字进行安全的转换:
let x: i32 = 5;
let y = x as i64;
有三种形式的安全转换:显式转换,数字类型之间的转换,和指针转换。
转换并不是可传递的:即便是e as U1 as U2
是一个有效的表达式,e as U2
也不必要是(事实上只有在U1
强转为U2
时才有效)。
e as U
是有效的仅当e
是T
类型而且T
能强转为U
。
e as U
的转换在如下情况下也是有效的:
e
是T
类型而且T
和U
是任意数值类型:numeric-cast
e
是一个类 C 语言枚举(变量并没有附加值),而且U
是一个整型:enum-cast
e
是bool
或char
而且T
是一个整型:prim-int-cast
e
是u8
而且U
是char
:u8-char-cast
例如:
let one = true as u8;
let at_sign = 64 as char;
let two_hundred = -56i8 as u8;
数值转换的语义是:
i32
->u32
)的转换是一个no-op
u32
->u8
)会截断u8
->u32
)会你也许会惊讶,[裸指针](Raw Pointers 裸指针.md)与整型之间的转换是安全的,而且不同类型的指针之间的转换遵循一些限制。只有解引用指针是不安全的:
let a = 300 as *const char; // a pointer to location 300
let b = a as u32;
e as U
在如下情况是一个有效的指针转换:
e
是*T
类型,U
是*U_0
类型,且要么U_0: Sized
要么unsize_kind(T) == unsize_kind(U_0)
:ptr-ptr-cast
e
是*T
类型且U
是数值类型,同时T: Sized
:ptr-addr-cast
e
是一个整型且U
是*U_0
类型,同时U_0: Sized
:addr-ptr-cast
e
是&[T; n]
类型且U
是*const T
类型:array-ptr-cast
e
是函数指针且U
是*T
类型,同时T: Sized
:fptr-ptr-cast
e
是函数指针且U
是一个整型:fptr-addr-cast
transmute
as
只允许安全的转换,并会拒绝例如尝试将 4 个字节转换为一个u32
:
let a = [0u8, 0u8, 0u8, 0u8];
let b = a as u32; // four eights makes 32
这个错误为:
error: non-scalar cast: `[u8; 4]` as `u32`
let b = a as u32; // four eights makes 32
^~~~~~~~
这是一个“非标量转换(non-scalar cast)”因为这里我们有多个值:四个元素的数组。这种类型的转换是非常危险的,因为他们假设多种底层结构的实现方式。为此,我们需要一些更危险的东西。
transmute
函数由[编译器固有功能](Intrinsics 固有功能.md)提供,它做的工作非常简单,不过非常可怕。它告诉Rust对待一个类型的值就像它是另一个类型一样。它这样做并不管类型检查系统,并完全信任你。
在我们之前的例子中,我们知道一个有4个u8
的数组可以正常代表一个u32
,并且我们想要进行转换。使用transmute
而不是as
,Rust允许我们:
use std::mem;
unsafe {
let a = [0u8, 0u8, 0u8, 0u8];
let b = mem::transmute::<[u8; 4], u32>(a);
}
为了使它编译通过我们要把这些操作封装到一个unsafe
块中。技术上讲,只有mem::transmute
调用自身需要位于块中,不过在这个情况下包含所有相关的内容是有好处的,这样你就知道该看哪了。在这例子中,a
的细节也是重要的,所以它们放到了块中。你会看到各种风格的代码,有时上下文离得太远,因此在unsafe
中包含所有的代码并不是一个好主意。
虽然transmute
做了非常少的检查,至少它确保了这些类型是相同大小的,这个错误:
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)
除了这些,你可以自行随意转换,只能帮你这么多了!