类型转换
类型转换分为隐式类型转换和显式类型转换。
隐式类型转换是由编译器完成的,开发者并未参与,所有又称强制类型转换。
显式类型转换是由开发者指定的,就是一般意义上的类型转换。
一、显式转换
(一)as
1.原生类型之间的转换
Rust不提供原生类型之间的隐式类型转换,但可以使用as关键字显式类型转换。
整数强转是最常用的强转。
整数强转就是二进制的重新解释。
从长的类型转到短的类型,要截取低位二进制。
语法格式
value as type
实例
let decimal = 65.4321_f32;
let integer: u8 = decimal; // 错误。不提供隐式转换
let integer = decimal as u8; // 正确。可以显式转换
let character = integer as char; //正确
println!("Casting: {} -> {} -> {}", decimal, integer, character);
println!("1000 as a u16 is: {}", 1000 as u16);
println!(" -1 as a u8 is : {}", (-1i8) as u8);
println!(" 128 as a i16 is: {}", 128 as i16);
println!(" 128 as a i8 is : {}", 128 as i8);
println!("1000 as a u8 is : {}", 1000 as u8); // 1000 as u8 -> 232
println!("1000 as a i8 is : {}", 1000 as i8); // 232的二进制补码是 -24
println!(" 232 as a i8 is : {}", 232 as i8);
let c = 'a' as u8; // 将char类型转换为u8类型,97
let mut values: [i32; 2] = [1, 2];
let p1: *mut i32 = values.as_mut_ptr();
let first_address = p1 as usize; // 将p1内存地址转换为一个整数
let second_address = first_address + 4; // 4 == std::mem::size_of::<i32>(),i32类型占用4个字节,因此将内存地址 + 4
let p2 = second_address as *mut i32; // 访问该地址指向的下一个整数p2
unsafe { *p2 += 1; }
assert_eq!(values[1], 3);
2.完全限定语法
类型A可以实现多个Trait,有些Trait有同名的函数,如果使用A去调用的话,编译器无法判断应该调用哪个函数,所以必须将A强转为特定的Trait,以告知编译器如何做出判断。这种将类型A强转成trait的用法就是完全限定语法。
完全限定语法的语法格式
<Type as Trait>::function(receiver_if_method, next_arg, ...);
实例
Pilot和Wizard都拥有方法fly。类型Human实现了这两个trait。
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
当调用Human实例的fly时,编译器默认调用直接实现在类型上的方法
fn main() {
let person = Human;
person.fly();
}
运行结果
*waving arms furiously*
为了调用Pilot 或Wizard的fly方法,在方法名前指定trait名向Rust澄清了我们希望调用哪个fly
fn main() {
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
运行结果
This is your captain speaking.
Up!
*waving arms furiously*
person.fly()也可以写成Human::fly(&person)。
然而,成员函数没有self参数。当两个类型实现了同一trait,Rust就不能判断是哪一个类型
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
}
运行结果
A baby dog is called a Spot
Dog有一个成员函数baby_name。Dog还实现了Animal trait。Animal也有一个成员函数baby_name
在main调用了Dog::baby_name函数,它直接调用Dog之上的成员函数。
这并不是我们需要的。我们希望调用的是Animal trait的baby_name函数,这样能够打印出A baby dog is called a puppy。
为了消歧义需要使用 完全限定语法
fn main() {
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
运行结果
A baby dog is called a puppy
3.类型和子类型相互转换
as转换可以用于类型和子类型之间的转换。比如&'static str和&'a str。'static生命期是整个进程存活期间有效的,而'a的生命期较短,我们称&'static是&'a的子类型,使用'static:'a来表示。as可以将父子类型自由转换,如&'static str as &'a str,这种做法的意义是为了满足某些函数对生命期的要求。
(二)From和Into特性
这是两个Trait,定义在标准库的convert模块中,其实他们做的是同一件事情
如果类型A实现了From<B>
,则B类型实例调用into方法就可以转换为类型A。
例如,我们常见的字符串String类型实现了From(&str),那么&str就可以into()为String。
所以我们只需要实现From即可
1.From
From trait允许一种类型定义 "怎么根据另一种类型生成自己",因此它提供了一种类型转换的简单机制。
比如,可以很容易地把 str 转换成 String:
let my_str = "hello";
let my_string = String::from(my_str);
也可以为自定义类型实现From
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);
}
2.Into
Into trait就是把 From trait倒过来而已。如果你为你的类型实现了 From,那么同时你也就免费获得了 Into。
例子
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);
}
(三)TryFrom 和 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() {
// TryFrom
assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
assert_eq!(EvenNumber::try_from(5), Err(()));
// TryInto
let 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特性
1.ToString
要把其他类型转换成 String,只需要实现那个类型的 ToString trait。然而不要直接这么做,您应该实现fmt::Display trait,它会自动提供 ToString,并且还可以用来打印
例子
use std::fmt;
struct Circle {
radius: i32
}
impl fmt::Display for Circle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Circle of radius {}", self.radius)
}
}
fn main() {
let circle = Circle { radius: 6 };
println!("{}", circle.to_string());
}
当然也可以直接实现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());
}
2.FromStr
把字符串转成其他类型。只要目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。
标准库中已经给无数类型实现了 FromStr。如果要转换到自定义类型,要手动实现 FromStr。
turbofish语法
语法格式
::<>
当把字符串转换成整数类型时,rust编译器无法确定要转换成哪种类型的整数,是u32还是i32。此时需要使用turbofish语法
实例
fn main() {
let parsed: i32 = "5".parse().unwrap(); //显式指定类型
let turbo_parsed = "10".parse::<i32>().unwrap(); //turbofish语法
let sum = parsed + turbo_parsed;
println!{"Sum: {:?}", sum};
}
(五)AsRef和AsMut特性
标准库中还有AsRef和AsMut两个trait,可以将值分别转换为不可变引用和可变引用。
比如,我们想设计一个允许传入&String和&str的函数
fn main(){
let str1 = "123".to_string();
let str2 = "456";
test(str1);
test(str2);
}
fn test(s: impl AsRef<str>){
println!("{}", s.as_ref());
}
如果我们自己定义一个类型,最好能实现AsRef这个Trait,它会给我们带来很多意想不到的实惠。
二、隐式转换
(一)自动强转点
发生自动强转的时机。
自动强转点有:
1.let语句中显式给出了类型。
例如,下面例子中 &mut 42自动强转成 &i8类型:
let _: &i8 = &mut 42;
2.静态项和常量项声明(类似于let语句)。
3.函数调用的参数
被强制的值是实参,它的类型被自动强转为形参的类型。
例如,下面例子中 &mut 42自动强转成 &i8类型:
fn bar(_: &i8) { }
fn main() {
bar(&mut 42);
}
对于方法调用,接受者(self参数)只能使用非固定尺寸类型自动强转
4.实例化结构体、联合体或枚举变体的字段。
例如,下面例子中 &mut 42自动强转成 &i8类型:
struct Foo<'a> { x: &'a i8 }
fn main() {
Foo { x: &mut 42 };
}
5.函数返回值
例如,下面例子中x将自动强转成 &dyn Display类型:
use std::fmt::Display;
fn foo(x: &u32) -> &dyn Display {
x
}
如果在自动强转点中的表达式是传播型表达式,那么该表达式中的对应子表达式也是自动强转点。传播从这些新的自动强转点开始递归。传播表达式及其相关子表达式有:
1.数组字面量,其数组的类型为 [U; n]。数组字面量中的每个子表达式都是自动强转到类型U的自动强转点。
2.重复句法声明的数组字面量,其数组的类型为 [U; n]。重复子表达式是用于自动强转到类型U的自动强转点。
3.元组,其中如果元组是自动强转到类型 (U_0, U_1, ..., U_n) 的强转点,则每个子表达式都是相应类型的自动强转点,比如第0个子表达式是到类型U_0的 自动强转点。
4.圆括号括起来的子表达式((e)):如果整个括号表达式的类型为U,则子表达式e是自动强转到类型U的自动强转点。
5.块:如果块的类型是U,那么块中的最后一个表达式(如果它不是以分号结尾的)就是一个自动强转到类型U的自动强转点。这里的块包括作为控制流语句的一部分的条件分支代码块,比如if/else,当然前提是这些块的返回需要有一个已知的类型。
(二)自动强转类型
1.T到U如果T是U的一个子类型 (反射性场景(reflexive case))
2.T_1到T_3当T_1可自动强转到T_2同时T_2又能自动强转到T_3 (传递性场景(transitive case))
注意这个还没有得到完全支持。
3.&mut T到 &T
4.*mut T到 *const T
5.&T到 *const T
6.&mut T到 *mut T
7.&T或 &mut T到 &U如果T实现了Deref Target = U。例如:
use std::ops::Deref;
struct CharContainer {
value: char,
}
impl Deref for CharContainer {
type Target = char;
fn deref<'a>(&'a self) -> &'a char {
&self.value
}
}
fn foo(arg: &char) {}
fn main() {
let x = &mut CharContainer { value: 'y' };
foo(x); //&mut CharContainer自动强转成 &char.
}
8.&mut T到 &mut U如果T实现了DerefMut Target = U
9.TyCtor(T) 到TyCtor(U),其中TyCtor(T) 是下列之一。TyCtor为类型构造器type constructor的简写。
&T
&mut T
*const T
*mut T
Box
并且U能够通过非固定尺寸类型自动强转得到。
10.非捕获闭包到函数指针
11.! 到任意T
(三)非固定尺寸类型自动强转
将固定尺寸类型转换为非固定尺寸类型
如果T可以自动强转成U,那么就会为T提供一个Unsize<U>
的内置实现:
1.[T; n] 到 [T]
2.T到dyn U, 当T实现U + Sized, 并且U是对象安全的时。
3.Foo<..., T, ...> 到Foo<..., U, ...>, 当:
Foo是一个结构体。
T实现了Unsize<U>
。
Foo的最后一个字段是和T相关的类型。
如果这最后一个字段是类型Bar<T>
,那么Bar<T>
实现了Unsized<Bar<U>>
。
T不是任何其他字段的类型的一部分。
此外,当T实现了Unsize<U>
或CoerceUnsized<Foo<U>>
时,类型Foo 可以实现CoerceUnsized<Foo<U>>
。这就允许Foo<T>
提供一个到Foo<U>
的非固定尺寸类型自动强转。
注:虽然非固定尺寸类型自动强转的定义及其实现已经稳定下来,但 Unsize 和 CoerceUnsized 这两个trait本身还没稳定下来,因此还不能直接用于稳定版的Rust。
(四)最小上界自动强转
在某些上下文中,编译器必须将多个类型强转到最公共的类型。这被称为"最小上界(Least Upper Bound,简称LUB)"自动强转。LUB自动强转只在以下情况中使用:
1.为一系列的if分支查找共同的类型。
2.为一系列的match分支查找共同的类型。
3.为数组元素查找共同的类型。
4.为带有多个返回语句的闭包查找共同的返回类型。
5.检查带有多个返回语句的函数的返回类型。
将一组类型T0...Tn自动强转到目标类型T_t,注意开始时T_t是未知的。LUB自动强转的计算过程是不断迭代的。首先把T_t定为T0。对于每一种新类型Ti,执行如下步骤:
1.如果Ti可以自动强转为当前目标类型T_t,则不做任何更改。
2.否则,检查T_t是否可以被自动强转为Ti;如果是这样,T_t就改为Ti。(此检查还取决于到目前为止所考虑的所有源表达式是否带有隐式自动强转。)
3.如果不是,尝试计算一个T_t和Ti的共同的超类型(supertype),此超类型将成为新的目标类型。
示例:
// if分支的情况
let bar = if true {
a
} else if false {
b
} else {
c
};
// 匹配臂的情况
let baw = match 42 {
0 => a,
1 => b,
_ => c,
};
// 数组元素的情况
let bax = [a, b, c];
// 多个返回项语句的闭包的情况
let clo = || {
if true {
a
} else if false {
b
} else {
c
}
};
let baz = clo();
// 检查带有多个返回语句的函数的情况
fn foo() -> i32 {
let (a, b, c) = (0, 1, 2);
match 42 {
0 => a,
1 => b,
_ => c,
}
}
在这些例子中,ba* 的类型可以通过LUB自动强转找到。编译器检查LUB自动强转在处理函数foo时,是否把a,b,c的结果转为了i32。