读Rust By Example(5)

类型转换

Rust 在基本类型之间没有提供隐式转换(强制类型转换)。显示类型转换可以使用 as 关键字。

一般来说,Rust 的整型类型的转换规则遵循 C 语言的惯例,除了那些在 C 语言是未定义行为的情况。在 Rust 中,所有的整型类型转换的行为都得到了很好的定义。

样例:

// 消除会溢出的类型转换的所有警告。
#![allow(overflowing_literals)]

fn main() {  
    let decimal = 65.4321_f32;

    // 错误,禁止隐式转换
    // let integer: u8 = decimal;

    // 显示转换 利用 `as` 关键字
    let integer = decimal as u8;
    let character = integer as char;

    println!("Casting: {} -> {} -> {}", decimal, integer, character);

    // 当将任意整数值转成无符号类型(unsigned 类型) T 时,
    // 将会加上或减去 std::T::MAX + 1,直到值符合新的类型

    // 1000 已符合 u16
    println!("1000 as a u16 is: {}", 1000 as u16);

    // 1000 - 256 - 256 - 256 = 232
    // 在计算机底层会截取数字的低8位(the least significant bit,LSB),
    // 而高位(the most significant bit,MSB)数字会被抛掉。
    // 此操作是按二进数存储的数字位进行
    println!("1000 as a u8 is : {}", 1000 as u8);
    // -1 + 256 = 255
    println!("  -1 as a u8 is : {}", (-1i8) as u8);

    // 对正数来说,与取余同理
    println!("1000 mod 256 is : {}", 1000 % 256);

    // 当将整数值转成有符号类型(signed 类型)时,同样要先将(二进制)数值
    // 转成相应的无符号类型(译注:如 i32 和 u32 对应,i16 和 u16对应),
    // 然后再求此值的补码(two's complement)。如果数值的最高位是 1,则数值
    // 是负数。

    println!(" 128 as a i16 is: {}", 128 as i16);
    // 128 as u8 -> 128
    println!(" 128 as a i8 is : {}", 128 as i8);

    // 1000 as u8 -> 232
    println!("1000 as a i8 is : {}", 1000 as i8);
    // and the two's complement of 232 is -24
    println!(" 232 as a i8 is : {}", 232 as i8);
}

字面量

数值字面量可以用后缀指定类型,如果想将字面量 42 定义为 i32 类型,直接写 42i32

无后缀的数值字面量的类型将取决于如何使用。如果没有限制的话,编译器将会默认将整数使用 i32,浮点数使用 f64

样例:

fn main() {  
    // 带后缀的字面量,在初始化的时候就明确了变量类型
    let x = 1u8;
    let y = 2u32;
    let z = 3f32;

    // 无后缀的字面量,将会在使用的时候决定变量类型
    let i = 1;
    let f = 1.0;

    // `size_of_val` 返回变量的位大小
    println!("size of `x` in bytes: {}", std::mem::size_of_val(&x));
    println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));
    println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));
    println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));
    println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));
}
  • fun(&foo) 是通过引用传参给一个函数,而不是通过值来传参(fun(foo))。更多内容参见 借用(borrowing)。
  • std::mem::size_of_val 是一个函数,不过是通过完整的路径调用的。代码可以划分到称为 模块(module)的逻辑单元中。在这个例子中,sizeofval 函数是定义在 mem 模块的, mem 模块是定义在 std 包(crate)中。更多内容参考模块 和 crate。

类型推导

类型推导引擎是相当智能的。它不仅仅在初始化期间分析右值的类型,还会通过分析变量在后面是怎么使用的来推导该变量的类型。这里给出一个类型推导的高级例子

样例

fn main() {  
    // 借助类型标注,编译器知道 `elem` 具有 u8 类型。
    let elem = 5u8;

    // 创建一个空 vector(可增长数组)。
    let mut vec = Vec::new();
    // 此时编译器并未知道 `vec` 的确切类型,它只知道 `vec` 是一个含有某种类型
    // 的 vector(`Vec<_>`)。

    // 将 `elem` 插入 vector。
    vec.push(elem);
    // Aha!现在编译器就知道了 `vec` 是一个含有 `u8` 类型的 vector(`Vec<u8>`)
    // 试一试 ^ 尝试将 `vec.push(elem)` 那行注释掉

    println!("{:?}", vec);
}

无需变量的类型标注,编译器和程序员都很开心!

别名

type 语句可以给一个已存在类型起一个新的名字。类型必须要有 CamelCase (驼峰方式)的名称,否则编译器会产生一个警告。对规则为例外的是基本类型: usizef32 等等。

样例

// `NanoSecond` 是 `u64` 的新名字。
type NanoSecond = u64;  
type Inch = u64;

// 使用一个属性来忽略警告。
#[allow(non_camel_case_types)]
type u64_t = u64;

fn main() {  
    // `NanoSecond` = `Inch` = `u64_t` = `u64`.
    let nanoseconds: NanoSecond = 5 as u64_t;
    let inches: Inch = 2 as u64_t;

    // 注意类型的别名没有提供任何额外的类型安全,因为别名不是新的类型
    println!("{} nanoseconds + {} inches = {} unit?",
             nanoseconds,
             inches,
             nanoseconds + inches);
}

别名的主要作用是减少按键,举个例子,IoResult<T> 类型是 Result<T, IoError> 类型的别名。