as 运算符有点像 C 中的强制类型转换,区别在于,它只能用于原始类型(i32 、i64 、f32 、
f64 、 u8 、 u32 、 char 等类型),并且它是安全的。
例
在 Rust 中,不同的数值类型是不能进行隐式转换的,比如:
let b: i64 = 1i32;会出现编译错误,提示无法进行类型转换。
error[E0308]: mismatched types --> src\main.rs:2:18 | 2 | let b: i64 = 1i32; | ^^^^ expected i64, found i32 help: change the type of the numeric literal from `i32` to `i64`这时可以使用as 进行转换。
let b: i64 = 1i32 as i64;
为什么它是安全的?
尝试以下代码:
let b = 1i32 as char;编译器错误:
error[E0604]: only `u8` can be cast as `char`, not `i32` --> src\main.rs:2:13 | 2 | let b = 1i32 as char; | ^^^^^^^^^^^^可见在不相关的类型之间,Rust 会拒绝转换,这也避免了运行时错误。
2. Trait From<T> 和 Into<T>上文说到,as 运算符之能在原始类型之间进行转换,那么对于 Struct 和 Enum 这样的类型该如何进行转换呢? 这就是我们这节的内容 From<T> 和 Into<T> 。
先来看一看这两个 Trait 的结构。
pub trait From<T> { fn from(T) -> Self; } pub trait Into<T> { fn into(self) -> T; }很简单,From<T> 有一个 from 方法,Into<T> 有一个 into 方法。
一般来说,我们应该尽量优先选择实现 From<T> 而不是 Into<T> ,因为当你为 U 实现 From<T> ,这意味着你同时也为 T 隐式实现了 Into<U> 。
来看个例子
fn main() { println!("Hello, world!"); let b: Complex = 1.into(); println!("{:?}", b); } #[derive(Debug)] struct Complex { re: i32, im: i32 } impl From<i32> for Complex{ fn from(re: i32) -> Self { Complex{ re, im:0 } } }当我为 Complex 实现 From<i32> 后,我也可以在 i32 上使用 into 方法,转换到 Complex 。
原始类型实现了与 as 转换相对应的 From<T> 与 Into<T> 。
当你为 U 实现 From<T> 之后,你要确保这个转换一定能成功,如若有失败的可能,你应该选择为 U 实现 TryFrom<T> 。
什么时候该使用 Into<T>
Into<T> 被设计出来,总有该用到的地方。那什么时候该使用呢?
先复习一下 Rust 中的 孤儿原则
在声明trait和impl trait的时候,Rust规定了一个Orphan Rule(孤儿规则):impl块要么与trait的声明在同一个的crate中,要么与类型的声明在同一个crate中。
也就是说,不能在一个crate中,针对一个外部的类型,实现一个外部的trait。
因为在其它的crate中,一个类型没有实现一个trait,很可能是有意的设计。
如果我们在使用其它的crate的时候,强行把它们“拉郎配”,是会制造出bug的。
比如说,我们写了一个程序,引用了外部库lib1和lib2,lib1中声明了一个trait T,lib2中声明了一个struct S ,我们不能在自己的程序中针对S实现T。
这也意味着,上游开发者在给别人写库的时候,尤其要注意。
一些比较常见的标准库中的 trait,比如 Display Debug ToString Default 等,应该尽可能地提供好。
否则,使用这个库的下游开发者,是没办法帮我们把这些 trait 实现的。
同理,如果是匿名impl,那么这个impl块必须与类型本身存在于同一个模块中。
来自 F001 https://zhuanlan.zhihu.com/p/21568827
显然, From<T> 不属于当前 crate ,当你要实现当前 crate 中的类型 T 转换到其他 crate 中的类型 U 时,如果选择为 U 实现 From<T> ,由于孤儿原则,编译器会阻止你这么做。这时我们就可以选择为 T 实现 Into<U> 。
注意,和 From<T> 不同,实现 Into<U> 之后并不会隐式实现 From<T> ,这点需特别注意。
From<T> 的妙用
回忆一下 Rust 的 ? 操作符,它被用于 返回值为 Result<T,E> 或者 Option<T> 的函数。回想一下,它是如何处理 Err(E) 的。
fn apply() -> Result<i32,i32> { Err(1) } fn main() -> Result<(),i64> { let a = apply()?; Ok(()) }上面的例子是可以通过编译的,既然 Rust 中的数值类型是不能隐式转换的,那么,当返回 Err(i32) 时是如何转换到 Err(i64) 的呢?这其实是一个 Rust 的语法糖。展开后的代码类似于下面:
fn apply() -> Result<i32,i32> { Err(1) } fn main() -> Result<(),i64> { let a = match apply() { Ok(v) => v, Err(e) => return Err(i64::from(e)), }; Ok(()) }也就是说,Rust 会自动调用目标类 from 方法进行转换。
3. 解引用强制多态这次先看一个例子:
fn print(message: &str) { println!("{}",message); } fn main() { let message: String = "message".to_string(); print(&message); }print 的形参是 &str 类型,然而在 main 中,我传递却是一个 &String 类型的实参。明显,这两个类型不相同!!Rust 为什么会通过这样的代码呢?
没错,这就是 Rust 的 解引用强制多态。
首先,需要了解一个 Deref Trait 。
#[lang = "deref"] pub trait Deref { type Target: ?Sized; #[must_use] fn deref(&self) -> &Self::Target; }deref 方法返回一个 &Target 类型的引用。