说到From
和Into
,以及从他们中衍生出的TryFrom
和TryInto
,想必大家都不会陌生。它们不像Borrow
、AsRef
、ToOwned
这些默默工作在泛型里的特征,是绝大多数Rust开发者每天都会使用到的东西。今天我们就来加深一下对这四个特征的了解吧~
From和Into
如果说AsRef
和AsMut
的功能是做“引用到引用”的转换的话,那么From
和Into
做的就是“值到值”的转换了:
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
的第一个小特点了:它们会立即把参数消耗掉。
对实现了From<T>
的类型U
,标准库为T
提供了Into<U>
的实现;也就是说,在为U
实现了From<T>
之后,就可以直接使用T::into()
来构造U
了:
use std::fmt;
struct BeautifulString(String);
impl From<String> for BeautifulString {
fn from(mut value: String) -> Self {
value.push_str("(✪ω✪)");
Self(value)
}
}
impl fmt::Display for BeautifulString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
fn main() {
let string = String::from("I am beautiful!");
let beautiful: BeautifulString = string.into(); // 看这里
println!("{}", beautiful); // I am beautiful!(✪ω✪)
}
除此以外,From
是反身的,也就是说对任何类型T
,都有T: From<T>
。
我们该实现From还是Into?
既然impl From<T> for U
之后可以自动获得impl Into<U> for T
,那么我们自然应该优先实现From
而不是Into
了;仅仅当转换的一方不是当前crate的成员时,才应当考虑实现Into
。最直观的例子就是我们可以为T
实现Into<String>
,但肯定不能为String
实现From<T>
,这违反了Rust的孤儿原则。
使用From和Into的原则
Rust文档中对From
和Into
的使用提出了以下的几条原则;它们中的一部分在技术角度上并无强制性,但遵循这些原则可以满足一般的用户预期:
- 转换应当是万无一失的:如果转换可能失败,那么应该使用
TryFrom
代替,而不是在From
和Into
的实现中埋下隐患,甚至产生panic。 - 转换应当是无损的:从语义上来讲,转换过程中不应该丢失或丢弃信息。例如,对
i32: From<u16>
来说,使用u16: TryFrom<i32>
可以将前一个过程的转换结果恢复原始值;但是对u16
和u32
来说,从u32
转换为u16
便不是无损的,因此也不该实现u16: From<u32>
。 - 转换应当是保值的:将
i8
转换为u8
是无损的——被转换为255的-1_i8可以被毫不费力地转换回255,但是我们不能因此就允许u8: From<i8>
的存在——毕竟-1和255是截然不同的两个数字,转换过程不保值。又比如说,String: From<u32>
是不存在的,因为身为1
的数字和身为"1"
的文本差别过大;而String: From<char>
便是可以接受的,因为'1'
和"1"
都是文本。 - 转换应当是显而易见的:转换应当是两种类型之间唯一合理的选择。例如,从
[u8;4]
转换成u32
的过程可以有多种选择:使用小字序、大字序和本地字序,所以应当分别为每种字节序实现不同的转换方法,而不是实现u32: From<[u8;4]>
。
TryFrom和TryInto
TryFrom
和TryInto
的功能和上文中介绍过的From
/Into
相同,但是它们可能会受控地失败:
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>;
}
通用实现
实现U: TryFrom<T>
会自动实现T: TryInto<U>
;TryFrom
也是反身的,任何类型T
都自动实现了TryFrom<T>
。
小插曲:
T: TryFrom<T>
是永远不会失败的,它的返回类型是Result<Self, Infallible>
。正如Infallible
的名字所言,它用于表示永远不会发生的错误。Infallible
将来会被!
替代。
这篇文章不算很长,简单地梳理了From
、Into
、TryFrom
和TryInto
四个用于“值到值”的转换的特征。下篇文章将会介绍Display
和ToString
特征。