在 Rust 核心库的 core::convert 模块中定义了一系列用于实现类型转换的特征。它们包括 AsRef、AsMut、From 与 Into、TryFrom 与 TryInto。
From 与 Into最简单的类型转换就是 From 与 Into 实现的从值到值的转换。它们共同定义了消耗源类型所有权的无条件转换。在这里,
From 或 Into 的类型实例会在调用实现的方法后自动失效;事实上,这两个特征是互为镜像的关系。从特征的名字就可以看出,实现了 From 的类型就获得了从彼类型转换到此类型的能力,而实现 Into 便是从此类型转换到彼类型的能力。举个例子:
我们经常使用的 String 类型就实现了 Into<Vec<u8>>,表明任何 String 都能被丝滑地转换为 Into<Vec<u8>>。现在我们可以对任何一个 String 调用 Into 提供的方法 into,结果是一个 Vec<u8>,且原先的 String 所有权被消耗。
而 String 类型还实现了 From<&str>,这表明任何 &str 都能无痛转换为 String。有一个经常用的从 &str 转换为 String 的方法:String::from,它接收一个 &str 并返回对应的 String,实际上这个方法就是 From 特征提供的。
从上面两个例子不难看出,这一对特征所定义的类型转换是互为逆向的。
以下是两个特征的定义细节:
pub trait From<T>: Sized {
// Required method
fn from(value: T) -> Self;
}
pub trait Into<T>: Sized {
// Required method
fn into(self) -> T;
}
文档中,官方建议尽量为类型实现 From,而不是选择 Into。这是因为当你为类型 U 实现 From<T> 时,Rust 会很贴心地自动为类型 T 实现 Into<U> 特征。但反过来却行不通。所以优先考虑实现 From,实在实现不了 From 再考虑 Into。
为什么只有 `Into<U>` 能被自动实现,而 `From<T>` 却不行?
这关系到孤儿原则(只有在特征或类型至少有一个属于当前 crate 时,我们才能对类型实现该特征。不能为外部类型实现外部特征)。
让我们考虑一个情景:ExternalType 是外部类型,而 MyType 来自当前 crate。假设我们手动为类型 MyType 实现了 Into<ExternalType>,现在 Rust 又自动提供了类型 ExternalType 的 From<MyType> 实现,但是你发现了什么?
ExternalType 是外部类型,而实现的特征 From 来自核心库,也就是同为外部特征。
两者都不属于当前 crate,违反了孤儿原则!
而在为泛型函数指定特征约束时,应优先使用 Into 而非 From,这样能够确保那些仅实现了 Into 的类型也能被正常使用。
TryFrom 与 TryInto正如特征名所包含的那样,TryFrom 与 TryInto 和 From 与 Into 极为相似,它们唯一的区别在于 TryFrom 与 TryInto 所进行的是可能失败的转换。由于转换可能不成功,某些情况下需要产生错误,所以这两种特征的方法都返回 Result:
pub trait TryFrom<T>: Sized {
type Error;
// Required method
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
// Required method
fn try_into(self) -> Result<T, Self::Error>;
}
举个例子,i32 实现了 TryFrom<i64> 特征,这意味着:
let big_number = 1_000_000_000_000i64;
// 由于 `big_number` 过大,超出 `i32` 可表示范围
// 转换会失败
let try_smaller_number = i32::try_from(big_number);
assert!(try_smaller_number.is_err());
// `as` 执行的是强制转换
// 所以从 `i64` 转换到 `i32` 会静悄悄地发生截断
let smaller_number = big_number as i32;
assert_eq!(smaller_number, -727379968);
AsRef 与 AsMut前面提到 From 与 Into 实现的从值到值的转换,并且会消耗掉源类型的所有权。如果我想把某类型的引用转换为另一类型的引用呢?
AsRef 和 AsMut 应运而生,它们分别用于低廉的引用到引用、可变引用到可变引用的转换。
Note
AsRef 和 AsMut 的出现并不代表着 From 与 Into 不能进行引用到引用(或可变引用到可变引用)的转换。
它们被设计出来是因为直接对类型 &U 实现 From<&T> 会产生代价高昂的转换,而 AsRef 和 AsMut 的转换成本则低廉得多。
特征名不带 Try,表明 AsRef 和 AsMut 都是不允许失败的转换。
两个特征的定义如下:
pub trait AsRef<T: PointeeSized>: PointeeSized {
// Required method
fn as_ref(&self) -> &T;
}
pub trait AsMut<T: PointeeSized>: PointeeSized {
// Required method
fn as_mut(&mut self) -> &mut T;
}
观察定义,你会发现 AsRef 和 AsMut 的用法与 Into 更像,而不是 From。
AsRef如果内部类型是引用或可变引用,AsRef 会自动解引用(例如当 foo 的类型为 &mut Foo 或 &&mut Foo 时,foo.as_ref() 的行为相同)。
需要注意的是,由于历史原因,上述行为目前并不适用于所有可解引用的类型。例如,foo.as_ref() 的行为与 Box::new(foo).as_ref() 并不相同。实际上许多智能指针提供的 as_ref 实现只是返回指向被指向值的引用(并未对该值执行廉价的引用到引用转换)。然而,AsRef::as_ref 不应仅仅为解引用而使用;如果只是为了解引用,应使用 Deref 强制转换:
let x = Box::new(5i32);
// 避免写成
// let y: &i32 = x.as_ref();
// 更好的写法是
let y: &i32 = &x;
对于那些实现了 Deref 的类型,可以利用 Deref 来实现 AsRef:
impl<T> AsRef<T> for SomeType
where
T: ?Sized,
<SomeType as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
如果函数接收一个 AsRef<T> 的泛型参数,那意味着所有可以转换为 &T 的引用类型都可作为参数传入。例如:
// `String` 和 `&str` 都实现了 `AsRef<str>`
fn is_hello<T: AsRef<str>>(s: T) {
assert_eq!("hello", s.as_ref());
}
let s = "hello";
is_hello(s);
let s = "hello".to_string();
is_hello(s);
AsMut如果内部类型是可变引用,AsMut 会自动解引用(例如当 foo 的类型为 &mut Foo 或 &mut &mut Foo 时,foo.as_mut() 的行为相同)。
需要注意的是,由于历史原因,上述行为目前并不适用于所有可可变解引用的类型。例如,foo.as_mut() 的行为与 Box::new(foo).as_mut() 并不相同。实际上许多智能指针提供的 as_mut 实现只是返回指向被指向值的可变引用(并未对该值执行廉价的可变引用到可变引用的转换)。然而,AsRef::as_ref 不应仅仅为可变解引用而使用;如果只是为了可变解引用,应使用 Deref 强制转换:
let mut x = Box::new(5i32);
// 避免写成
// let y: &mut i32 = x.as_mut();
// 更好的写法是
let y: &mut i32 = &mut x;
对于那些实现了 DerefMut 的类型,可以利用 DerefMut 来实现 AsMut:
impl<T> AsMut<T> for SomeType
where
<SomeType as Deref>::Target: AsMut<T>,
{
fn as_mut(&mut self) -> &mut T {
self.deref_mut().as_mut()
}
}
如果函数接收一个 AsMut<T> 的泛型参数,那意味着所有可以转换为 &mut T 的可变引用类型都可作为参数传入。
Important
一个类型可以有多个 AsMut 的实现。例如 Vec<T> 同时实现了 AsMut<Vec<T>> 和 AsMut<[T]>。
as 关键字as 最常见的用途是将原生类型转换为其他原生类型,但它还有其他用途,包括将指针转换为地址、将地址转换为指针,以及将指针转换为其他指针等。
语法格式是 e as U。其中 e 是表达式,U 是无约束的类型。
e 的类型 |
U |
e as U 执行的转换 |
|---|---|---|
| 整数或浮点类型 | 整数或浮点类型 | 数值转换 |
| 枚举 | 整数类型 | 枚举转换 |
bool 或 char |
整数类型 | 原生类型到整数的转换 |
u8 |
char |
u8 到 char 转换 |
*T |
*V(当兼容时) |
指针到指针转换 |
*T(其中 T: Sized) |
整数类型 | 指针到地址转换 |
| 整数类型 | *V(其中 V: Sized) |
地址到指针转换 |
&m₁ [T; n] |
*m₂ T1 |
数组到指针转换 |
*m₁ [T; n] |
*m₂ T1 |
数组到指针转换 |
| 函数项 | 函数指针 | 函数项到函数指针转换 |
| 函数项 | *V(其中 V: Sized) |
函数项到指针转换 |
| 函数项 | 整数 | 函数项到地址转换 |
| 函数指针 | *V(其中 V: Sized) |
函数指针到指针转换 |
| 函数指针 | 整数 | 函数指针到地址转换 |
| 闭包2 | 函数指针 | 闭包到函数指针转换 |