类型系统
原生类型的显式类型转换
Rust 不提供原生类型之间的隐式类型转换,但可以使用 as 关键字进行显式类型转换(casting)。
// 不显示类型转换产生的溢出警告。
#![allow(overflowing_literals)]fn main() {let decimal = 65.4321_f32;// 错误!不提供隐式转换// let integer: u8 = decimal;// 可以显式转换let integer = decimal as u8;let character = integer as char;println!("Casting: {} -> {} -> {}", decimal, integer, character); // Casting: 65.4321 -> 65 -> A// 当把任何类型转换为无符号类型 T 时,会不断加上或减去 (std::T::MAX + 1) 直到值位于新类型 T 的范围内。// 1000 已经在 u16 的范围内println!("1000 as a u16 is: {}", 1000 as u16); // 1000 as a u16 is: 1000// 1000 - 256 - 256 - 256 = 232 事实上的处理方式是:从最低有效位开始保留 8 位,然后剩余位置都被抛弃。println!("1000 as a u8 is : {}", 1000 as u8); // 1000 as a u8 is : 232// -1 + 256 = 255println!(" -1 as a u8 is : {}", (-1i8) as u8); // -1 as a u8 is : 255// 对正数,这就和取模一样。println!("1000 mod 256 is : {}", 1000 % 256); // 1000 mod 256 is : 232// 当转换到有符号类型时,结果就和 “先转换到对应的无符号类型,如果最高有效位是 1,则该值为负” 是一样的。// 当然如果数值已经在目标类型的范围内,就直接把它放进去。println!(" 128 as a i16 is: {}", 128 as i16); // 128 as a i16 is: 128// 128 转成 u8 还是 128,但转到 i8 相当于给 128 取八位的二进制补码,其值是:println!(" 128 as a i8 is : {}", 128 as i8); // 128 as a i8 is : -128// 232 的二进制补码是 -24println!(" 232 as a i8 is : {}", 232 as i8); // 232 as a i8 is : -24
}
字面量
对数值字面量,只要把类型作为后缀加上去,就完成了类型说明。比如指定字面量 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)); // size of `x` in bytes: 1println!("size of `y` in bytes: {}", std::mem::size_of_val(&y)); // size of `y` in bytes: 4println!("size of `z` in bytes: {}", std::mem::size_of_val(&z)); // size of `z` in bytes: 4println!("size of `i` in bytes: {}", std::mem::size_of_val(&i)); // size of `i` in bytes: 4println!("size of `f` in bytes: {}", std::mem::size_of_val(&f)); // size of `f` in bytes: 8
}
类型推断
Rust 的类型推断引擎是很聪明的,它不只是在初始化时看看右值的类型而已,它还会考察变量之后会怎样使用,借此推断类型。这是一个类型推导的进阶例子:
fn main() {// 因为有类型说明,编译器知道 `elem` 的类型是 u8。let elem = 5u8;// 创建一个空向量,现在编译器还不知道 `vec` 的具体类型,只知道它是某种东西构成的向量(`Vec<_>`)let mut vec = Vec::new();// 在向量中插入 `elem`。vec.push(elem); // 现在编译器知道 `vec` 是 u8 的向量了(`Vec<u8>`)。println!("{:?}", vec);
}
别名
可以用 type 语句给已有的类型取个新的名字。类型的名字必须遵循驼峰命名法(像是 CamelCase 这样),否则编译器将给出警告。
type NanoSecond = u64;
type Inch = u64;fn main() {let nanoseconds: NanoSecond = 5 as NanoSecond;let inches: Inch = 2 as Inch;println!("{} nanoseconds + {} inches = {} unit?", nanoseconds, inches, nanoseconds + inches);
}
别名的主要用途是避免写出冗长的模板化代码。如 IoResult<T>
是 Result<T, IoError>
类型的别名。
类型转换
Rust 使用 trait 解决类型之间的转换问题。最常用的转换会用到 From 和 into 两个 trait。
From 和 Into
From 和 Into 两个 trait 是内部相关联的,实际上这是它们实现的一部分。如果我们能够从类型 B 得到类型 A,那么很容易相信我们也能把类型 B 转换为类型 A。
From
From trait 允许一种类型定义 “怎么根据另一种类型生成自己”,因此它提供了一种类型转换的简单机制。在标准库中有无数 From 的实现,规定原生类型及其他常见类型的转换功能。
比如,可以很容易地把 str 转换成 String:
fn main() {let my_str = "hello";let my_string = String::from(my_str);
}
也可以为我们自己的类型定义转换机制:
use std::convert::From;#[derive(Debug)]
struct Number {value: i32,
}impl From<i32> for Number {fn from(item: i32) -> Self {Number { value: item }}
}fn main() {let num = Number::from(30);println!("My number is {:?}", num); // My number is Number { value: 30 }
}
Into
Into trait 就是把 From trait 倒过来而已。也就是说,如果你为你的类型实现了 From,那么同时你也就免费获得了 Into。使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断它。
use std::convert::From;#[derive(Debug)]
struct Number {value: i32,
}impl From<i32> for Number {fn from(item: i32) -> Self {Number { value: item }}
}fn main() {let int = 5;// 需要指明类型说明let num: Number = int.into();println!("My number is {:?}", num); // My number is Number { value: 5 }
}
TryFrom and TryInto
类似于 From 和 Into,TryFrom 和 TryInto 是类型转换的通用 trait。不同于 From/Into 的是,TryFrom 和 TryInto trait 用于易出错的转换,其返回值是 Result 型。
use std::convert::TryFrom;
use std::convert::TryInto;#[derive(Debug, PartialEq)]
struct EvenNumber(i32);impl TryFrom<i32> for EvenNumber {type Error = ();fn try_from(value: i32) -> Result<Self, Self::Error> {if value % 2 == 0 {Ok(EvenNumber(value))} else {Err(())}}
}fn main() {// TryFromassert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));assert_eq!(EvenNumber::try_from(5), Err(()));// TryIntolet result: Result<EvenNumber, ()> = 8i32.try_into();assert_eq!(result, Ok(EvenNumber(8)));let result: Result<EvenNumber, ()> = 5i32.try_into();assert_eq!(result, Err(()));
}
ToString 和 FromStr
ToString
要把任何类型转换成 String,只需要实现那个类型的 ToString trait。然而不要直接这么做,实现 fmt::Display trait,它会自动提供 ToString,并且还可以用来打印类型。
use std::string::ToString;struct Circle {radius: i32
}impl ToString for Circle {fn to_string(&self) -> String {format!("Circle of radius {:?}", self.radius)}
}fn main() {let circle = Circle { radius: 6 };println!("{}", circle.to_string()); // Circle of radius 6
}
解析字符串
我们经常需要把字符串转成数字。完成这项工作的标准手段是用 parse 函数。我们得提供要转换到的类型,这可以通过不使用类型推断。
只要对目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。标准库中已经给无数种类型实现了 FromStr。如果要转换到用户定义类型,只要手动实现 FromStr 就行。
fn main() {let parsed: i32 = "5".parse().unwrap();let turbo_parsed = "10".parse::<i32>().unwrap();let sum = parsed + turbo_parsed;println!{"Sum: {:?}", sum}; // Sum: 15
}