类型转换
Rust 是强类型语言,同时也是强安全语言,它内置了一些基本类型之间的转换,如as
操作符: if a < (b as i32) {...}。通常我们是
把范围较小的类型转换成较大的类型,来避免数据超范围。常见的一些转换形式:
fn main() {
let a = 3.1 as i8;
let b = 100_i8 as i32;
let c = 'a' as u8; // 将字符'a'转换为整数,97
println!("{},{},{}",a,b,c)
}
如果要处理转换错误,那么可以使用 TryInto特征,该
特征通过std::prelu模块提前引入到了当前作用域中,try_into
会尝试进行一次转换,并返回一个 Result
,此时就可以对其进行相应的错误处理,例如,try_into
转换会捕获大类型向小类型转换时导致的溢出错误:
fn main() {
let b: i16 = 1500;
let b_: u8 = match b.try_into() {
Ok(b1) => b1,
Err(e) => {
println!("{:?}", e.to_string());
0
}
};
}
运行后输出如下 "out of range integral type conversion attempted"
as
和TryInto
只能应用在数值类型上 ,在其他类型转换时需要强制转换,在某些情况下,类型是可以进行隐式强制转换的。但在匹配特征时,不会做任何强制转换(除了方法)。
自定义类型
newtype指的是使用元组结构体的方式将已有的类型包裹起来,例如:struct Meters(u32);这里
Meters
就是一个 newtype;其主要作用有:1、为
外部类型实现外部特征。比如前面说到的:如果要为类型 A
实现特征 T
,那么 A
或者 T
必须至少有一个在当前的作用范围内。否则就得遵循孤儿规则。2、更好的可读性及类型异化。隐藏内部类型的细节
use std::ops::Add;
use std::fmt;
struct Meters(u32);
impl fmt::Display for Meters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "目标地点距离你{}米", self.0)
}
}
impl Add for Meters {
type Output = Self;
fn add(self, other: Meters) -> Self {
Self(self.0 + other.0)
}
}
fn main() {
let d = calculate_distance(Meters(10), Meters(20));
println!("{}", d);
}
fn calculate_distance(d1: Meters, d2: Meters) -> Meters {
d1 + d2
}
//输出:
目标地点距离你30米
上面代码创建了一个 newtype
Meters,为其实现 Display
和 Add
特征,接着对两个距离进行求和计算,最终打印出该距离
除了使用 newtype,我们还可以使用类型别名来创建新类型,但
类型别名仅仅是别名,只是为了让可读性更好,并不是全新的类型,而且类型别名无法实现为外部类型实现外部特征等功能。然而类型别名可以让类型可读性更好,还能减少模版代码的使用。在标准库中,类型别名应用最广的就是简化 Result<T, E>
枚举,在使用 std::io
库时,它的所有错误类型都是 std::io::Error。而使用别名之后,std::io::Result<T>
即可替代冗长的 std::result::Result<T, std::io::Error>
类型。
type Result<T> = std::result::Result<T, std::io::Error>;
panic
的返回值是 !
,代表它决不会返回任何值。
定长与不定长类型
从编译器何时能获知类型大小的角度来看,类型分为定长类型(sized)与不定长类型( unsized ),它的大小只有到了程序运行时才能动态获知,又被称之为 DST;切片和str都是一个典型的 DST 类型,str既不是 String
动态字符串,也不是 &str
字符串切片,它是 String
和 &str
的底层数据类型,属于是一个动态类型。而 &str
的引用有了底层堆数据的明确信息,它的引用存储在栈上,具有固定大小,因此是固定大小类型。所以,将动态数据固定化的方法就是使用引用指向这些动态数据,然后在引用中存储相关的内存位置、长度等信息。
let s1: str = "Hello there!";// error
let s3: &str = "on?"// ok
而对于泛型参数 T,编译器自动帮我们加上了 Sized
特征约束,表示泛型函数只能用于一切实现了 Sized
特征的类型,以保证T
是固定大小的类型,几乎所有类型都实现了 Sized
特征。如果确实需要在泛型函数中使用动态数据类型,则需使用 ?Sized
特征,表明类型 T
既有可能是固定大小的类型,也可能是动态大小的类型。
fn generic<T: ?Sized>(t: &T) {
// --snip--
}
Rust 中常见的 DST
类型有: str
、[T]
、dyn Trait
,它们都无法单独被使用,必须要通过引用或者 Box
来间接使用 。
fn foobar_1(thing: &dyn MyThing) {} // OK
fn foobar_2(thing: Box<dyn MyThing>) {} // OK
fn foobar_3(thing: MyThing) {} // ERROR!
每一个特征都是一个可以通过名称来引用的动态大小类型。因此如果想把特征作为具体的类型来传递给函数,必须将其转换成一个特征对象:诸如 &dyn Trait
或者 Box<dyn Trait>
(还有 Rc<dyn Trait>
);使用 Box
可以将一个动态大小的特征变成一个具有固定大小的特征对象
let s1: Box<str> = "Hello there!".into();
在 Rust 中,枚举很容易转换成整数,例如:let x = MyEnum::C as i32;但整数却不能直接转换成枚举,常用的方法有:1、使用三方库num-traits
和num-derive。2、使用TryFrom
特征。3、使用std::mem::transmute直接进行转换,例如 let z: MyEnum = unsafe { std::mem::transmute(y) };