• 类型转换
    • 强制转换(Coercion)
    • as
      • 显式转换(Explicit coercions)
      • 数值转换
      • 指针转换
    • transmute

    类型转换

    casting-between-types.md


    commit ccb1d87d6faa9ff528d22b96595a0e2cbb16c0f2

    Rust,和它对安全的关注,提供了两种不同的在不同类型间转换的方式。第一个,as,用于安全转换。相反,transmute允许任意的转换,而这是 Rust 中最危险的功能之一!

    强制转换(Coercion)

    类型间的强制转换是隐式的并没有自己的语法,不过可以写作as

    强转出现在letconststatic语句;函数调用参数;结构体初始化的字符值;和函数返回值中。

    最常用的强转的例子是从引用中去掉可变性:

    • &mut T&T

    一个相似的转换时去掉一个裸指针的可变性:

    • *mut T*const T

    引用也能被强转为裸指针:

    • &T*const T
    • &mut T*mut T

    自定义强转可以用Deref定义。

    强转是可传递的。

    as

    as关键字进行安全的转换:

    1. let x: i32 = 5;
    2. let y = x as i64;

    有三种形式的安全转换:显式转换,数字类型之间的转换,和指针转换。

    转换并不是可传递的:即便是e as U1 as U2是一个有效的表达式,e as U2也不必要是(事实上只有在U1强转为U2时才有效)。

    显式转换(Explicit coercions)

    e as U是有效的仅当eT类型而且T能强转为U

    数值转换

    e as U的转换在如下情况下也是有效的:

    • eT类型而且TU是任意数值类型:numeric-cast
    • e是一个类 C 语言枚举(变量并没有附加值),而且U是一个整型:enum-cast
    • eboolchar而且T是一个整型:prim-int-cast
    • eu8而且Ucharu8-char-cast

    例如:

    1. let one = true as u8;
    2. let at_sign = 64 as char;
    3. let two_hundred = -56i8 as u8;

    数值转换的语义是:

    • 两个相同大小的整型之间(例如:i32->u32)的转换是一个no-op
    • 从一个大的整型转换为一个小的整型(例如:u32->u8)会截断
    • 从一个小的整型转换为一个大的整型(例如:u8->u32)会
      • 如果源类型是无符号的会补零(zero-extend)
      • 如果源类型是有符号的会符号(sign-extend)
    • 从一个浮点转换为一个整型会向 0 舍入
      • 注意:目前如果舍入的值并不能用目标整型表示的话会导致未定义行为(Undefined Behavior)。这包括 Inf 和 NaN。这是一个 bug 并会被修复。
    • 从一个整型转换为一个浮点会产生整型的浮点表示,如有必要会舍入(未指定舍入策略)
    • 从 f32 转换为 f64 是完美无缺的
    • 从 f64 转换为 f32 会产生最接近的可能值(未指定舍入策略)
      • 注意:目前如果值是有限的不过大于或小于 f32 所能表示的最大最小值会导致未定义行为(Undefined Behavior)。这是一个 bug 并会被修复。

    指针转换

    你也许会惊讶,裸指针与整型之间的转换是安全的,而且不同类型的指针之间的转换遵循一些限制。只有解引用指针是不安全的:

    1. let a = 300 as *const char; // `a` is a pointer to location 300.
    2. 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: Sizedptr-addr-cast
    • e是一个整型且U*U_0类型,同时U_0: Sizedaddr-ptr-cast
    • e&[T; n]类型且U*const T类型:array-ptr-cast
    • e是函数指针且U*T类型,同时T: Sizedfptr-ptr-cast
    • e是函数指针且U是一个整型:fptr-addr-cast

    transmute

    as只允许安全的转换,并会拒绝例如尝试将 4 个字节转换为一个u32

    1. let a = [0u8, 0u8, 0u8, 0u8];
    2. let b = a as u32; // Four u8s makes a u32.

    这个错误为:

    1. error: non-scalar cast: `[u8; 4]` as `u32`
    2. let b = a as u32; // Four u8s makes a u32.
    3. ^~~~~~~~

    这是一个“非标量转换(non-scalar cast)”因为这里我们有多个值:四个元素的数组。这种类型的转换是非常危险的,因为他们假设多种底层结构的实现方式。为此,我们需要一些更危险的东西。

    transmute函数由编译器固有功能提供,它做的工作非常简单,不过非常可怕。它告诉Rust对待一个类型的值就像它是另一个类型一样。它这样做并不管类型检查系统,并完全信任你。

    在我们之前的例子中,我们知道一个有 4 个u8的数组可以正常代表一个u32,并且我们想要进行转换。使用transmute而不是as,Rust 允许我们:

    1. use std::mem;
    2. fn main() {
    3. unsafe {
    4. let a = [0u8, 1u8, 0u8, 0u8];
    5. let b = mem::transmute::<[u8; 4], u32>(a);
    6. println!("{}", b); // 256
    7. // Or, more concisely:
    8. let c: u32 = mem::transmute(a);
    9. println!("{}", c); // 256
    10. }
    11. }

    为了使它编译通过我们要把这些操作封装到一个unsafe块中。技术上讲,只有mem::transmute调用自身需要位于块中,不过在这个情况下包含所有相关的内容是有好处的,这样你就知道该看哪了。在这例子中,a的细节也是重要的,所以它们放到了块中。你会看到各种风格的代码,有时上下文离得太远,因此在unsafe中包含所有的代码并不是一个好主意。

    虽然transmute做了非常少的检查,至少它确保了这些类型是相同大小的,这个错误:

    1. use std::mem;
    2. unsafe {
    3. let a = [0u8, 0u8, 0u8, 0u8];
    4. let b = mem::transmute::<[u8; 4], u64>(a);
    5. }

    和:

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

    除了这些,你可以自行随意转换,只能帮你这么多了!