Rust 是类型安全的语言,因此在Rust 中做类型转换不是一件简单的事,这一章节,我们将对Rust 中的类型转换进行详尽讲解。
高能预警,本章节有些难,可以考虑学了进阶后回头再看
as 转换
先来看一段代码
rust
fn main() {
let a: i32 = 10;
let b: u16 = 100;
if a < b {
println!("Ten is less than one hundred.");
}
}
能跟着这本书一直学习到这里,说明你对Rust 已经有了一定的理解,那么一眼就能看出这段代码注定会报错,因为a 和 b 拥有不同的类型, Rust 不允许两种不同的类型进行比较。
解决办法很简单,只要把 a 转换成 i32 类型即可, Rust 中内置了一些基本类型之间的转换,这里使用 as 操作来完成: if a < (b as i32) {...} ,那么为什么不把 a 转换成 u16类型呢?
因为每个类型能表达的数据范围不同,如果把范围较大的类型转换成较小的类型,会造成错误,因此我们需要把范围较小的类型转换成较大的类型,来避免这些问题的发生。
使用类型转换需要小型,因为如果执行以下操作 300_i32 as i8 , 你将获得 44 这个值,而不是 300 ,因为 i8 类型表达的最大值 2^7 - 1 ,使用以下代码可以查看 i8 的最大值
rust
let a = i8::MAX;
println!("{}",a);
下面列出了常用的转换形式
rust
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);
}
内存地址转换为指针
rust
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 += 2;
}
assert_eq!(values[1],3);
强制类型转换的边角知识
1.转换不具有传递性,就算 e as U1 as U2 是合法的, 也不能说明 e as U2 是合法的(e 不能直接转换成 U2)
TryInto 转换
在一些场景中,使用as 关键字会有比较大的限制,如果你想要在类型转换上拥有完全的控制而不依赖内置的转换,例如处理转换错误,那么可以使用TryInto
rust
use std::convert::TryInto;
fn main() {
let a: u8 = 10;
let b: u16 = 1500;
let b_: u8 = b.try_info().unwrap();
if a < b_ {
println!("Ten is less than one hundred.");
}
}
上面代码中引入了 std::convert::TryInto 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中,**我们在上面用到了try_into 方法, 因此需要引入对应的特征,但是Rust 又提供了一个非常遍历的方法,把最常用的标准库中的特征通过std::prelude模块提前引入到当前作用域中,其中包括了 std::convert::TryInto ,你可以尝试删除第一行代码 use ... 看看是否会报错。
try_info 会尝试进行依次转换,并返回一个Result ,此时就可以对其进行响应的错误处理,由于我们的例子,只是为了快速测试,因此使用 unwrap 方法, 该方法在发现错误是,会直接调用 panic 导致程序的崩溃退出,在实际项目中,请不要这么使用,具体见panic 部分。
最主要的是 try_info 转换会捕获大类型向小类型转换时导致的溢出错误
rust
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" , 在这里我们程序捕获了错误,编译器告诉我们类型范围超出的转换是不被允许的,因为我们试图把 1500_i16 转换为 u8类型,后者明显不足以承载这么大的值。
通用类型转换
虽然 as 和 TryInto 很强大,类型是可以进行隐式强制转换的,虽然这些转换弱化了Rust 的类型系统,但是它们的存在是为了让rust 在大多数场景可以工作,而不是报各种类型上的编译错误。
首先,在匹配特征时, 不会做任何强制转换(除了方法),一个类型T 可以强制转换为U , 不代表 impl T 可以强制转换为 impl U, 例如下面的代码就无法通过编译检查
rust
trait Trait {}
fn foo<X: Trait>(t: X) {}
impl<'a> Trait for & 'a i32 {]
fn main() {
let t: &mut i32 = &mut 0;
foo(t);
}
报错如下:
error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
--> src/main.rs:9:9
|
9 | foo(t);
| ^ the trait `Trait` is not implemented for `&mut i32`
|
= help: the following implementations were found:
<&'a i32 as Trait>
= note: `Trait` is implemented for `&i32`, but not for `&mut i32`
&i32 实现了特征 Trait ,&mut i32 可以转换为 &i32,但是 &mut i32 依然无法作为 Trait 来使用。
点操作符
方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用,自动解引用,强制类型转换直接到类型能匹配等。
假设有一个方法 foo ,它有一个接收器(接收器就是 self ,&self ,&mut self 参数),如果调用 value.foo() ,编译器在调用 foo 之前, 需要决定到底使用哪个 Self 类型来调用,现在假设value 拥有类型 T
在进一步,我们使用完全限定语法来进行准的函数调用
首先,编译器检查它是否可以直接调用 T::foo(value), 称职位值方法调用
如果上一步调用无法完成(例如方法类型错误或者特征没有针对Self 进行实现,上文提到过特征不能进行强制转换),那么编译器会尝试增加自动引用,例如会尝试以下调用: <&T>::foo(value) 和 <&mut T>::foo(value) ,称之为引用方法调用
若上面两个方法依然不工作,编译器会试着解引用T ,然后在进行尝试,这里使用了Deref特征-- 若 T: Deref<Target = U> (T 可以被解引用为 U),那么编译器会使用U 类型进行尝试,称之为解引用方法调用
若T 不能被解引用,且 T 是一个定长类型,在编译期类型长度是已知的,那么编译器也会尝试将T 从定长类型转为不定长类型,例如将 [i32;2 ] 转为 [i32]
若还是不行,那。。。没有那了 , 最后编译器大喊一声,汝欺我甚,
下面我们来用一个例子来解释上面的方法查找算法:
rust
let array: Rc<Box<[T; 3]>> = ....;
let first_entry = array[0];
array 数组的底层数据隐藏在了重重封锁之后,那么编译器如何使用array[0] 这种数组原生访问语法通过重重封锁,准确的访问到数组中的第一个元素?
首先, array[0] 只是 Index 特征的语法糖: 编译器会将 array[0] 转换为 array.index(0) 调用,当然在调用之前,编译器会先检查 array 是否实现了 Index 特征
接着 编译器检查 Rc<Box<[T; 3]>> 是否有实现 Index 特征,结果是否,不仅如此,&Rc<Box<[T; 3]>> 与 &mut Rc<Box<[T; 3]>> 也没有实现
上面的都不能工作,编译器开始对Rc<Box<[T;3]>> 进行解引用,把它转编程 Box<[T; 3]>
此时继续对 Box<[T; 3]> 进行上面的操作 : Box<[T; 3]> ,&Box<[T; 3]> ,和 &mut Box<[T; 3]> 都没有实现 Index 特征,所以编译器开始对 Box<[T; 3]> 进行解引用,然后我们得到了 [T ; 3]
T; 3\] 以及它的各种引用都没有实现 Index 索引是不是很反直觉:D ,在直觉中, 数组都可以通过索引访问,实际上只有数组切片才可以! ,它也不能再进行解引用,因此编译器只能祭出最后的大杀器: 将定长转为不定长,因此\[T; 3\] 被转换成\[T\] ,也就是数组切片,它实现了 Index 特征,因此最终我们可以通过 index 方法访问到对应的元素
过程看起来很复杂,但是也还好,挺好理解,如果你现在不能彻底理解,也不要紧,等以后对Rust理解更深了,同时需要深入理解类型转换是,再来细细品读本章
过程看起来很复杂,但是也还好,挺好理解,如果你现在不能彻底理解,也不要紧,等以后对Rust理解更深了,同时需要深入理解类型转换是,再来细细品读本章。
再来看看以下更复杂的例子
```rust
fn do_stuff