介绍
TryFrom
和 TryInto
两个 trait 均源自于 std::convert
模块,它们在类型转换当中扮演着重要角色。
-
TryFrom
和TryInto
都会消耗原始类型的值(即获取其所有权),并将其转换为另一种类型的值,最终返回Result
类型。 -
应该始终优先实现
TryFrom
而不是TryInto
,因为实现TryFrom
后会自动通过标准库中的通用实现提供对应TryInto
的实现。 -
为泛型函数指定 trait 约束时,优先使用
TryInto
而不是TryFrom
,这样对于只实现了TryInto
而没有实现TryFrom
的类型也可以作为参数来使用。 -
与
From
和Into
不同,TryFrom
和TryInto
允许类型转换出现失败,并且失败后能够优雅地返回错误。
定义
我们首先来看看 TryFrom
和 TryInto
的定义:
rust
trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
它们等价于下面的形式:
rust
trait TryFrom<T> where Self: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
trait TryInto<T> where Self: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
Self: Sized
是一种类型约束,用于表示当前的类型 Self
必须是一个在编译时大小已知的类型,即实现了 Sized
trait 的类型。
也就是说 TryFrom
和 TryInto
只能被那些在编译时大小已知的类型所实现。
rust
struct Foo {
value: bool,
}
// ok
impl TryFrom<bool> for Foo {
type Error = &'static str;
fn try_from(value: bool) -> Result<Self, Self::Error> {
Ok(Foo { value })
}
}
// error
impl TryFrom<bool> for [Foo] {
type Error = &'static str;
fn try_from(_: bool) -> Result<Self, Self::Error> {
Err("Error")
}
}
fn main() {
}
上述代码中,由于 [Foo]
是 DST 类型,编译期间大小未知,所以无法实现 TryFrom<bool>
。
示例
rust
struct Small {
value: i8,
}
struct Big {
value: i64,
}
impl TryFrom<Big> for Small {
type Error = &'static str;
fn try_from(big: Big) -> Result<Self, Self::Error> {
if big.value >= i8::MIN as i64 && big.value <= i8::MAX as i64 {
Ok(Small { value: big.value as i8 })
}
else {
Err("overflow")
}
}
}
fn main() {
let small: Result<Small, &str> = Small::try_from(Big { value: 1_000_000_i64 });
// overflow
match small {
Ok(value) => println!("{}", value.value),
Err(err) => println!("{}", err),
}
let small: Result<Small, &str> = Big { value: 1_i64 }.try_into();
// 1
match small {
Ok(value) => println!("{}", value.value),
Err(err) => println!("{}", err),
}
}
rust
struct Small {
value: i8,
}
struct Big {
value: i64,
}
impl TryFrom<Big> for Small {
type Error = &'static str;
fn try_from(mut big: Big) -> Result<Self, Self::Error> {
big.value %= i8::MAX as i64;
Ok(Small { value: big.value as i8 })
}
}
fn main() {
let small: Result<Small, &str> = Small::try_from(Big { value: 1_000_000_i64 });
// 2
if let Ok(ok) = small {
println!("{}", ok.value);
}
let small: Result<Small, &str> = Big { value: -523_i64 }.try_into();
// -15
if let Ok(ok) = small {
println!("{}", ok.value);
}
}
自动实现 TryInto
自定义类型在实现 TryFrom
后,即可直接调用 try_into
函数,这是因为标准库自动帮我们实现了 TryInto
,我们来看看标准库是如何帮我们实现的:
rust
impl<T, U> TryInto<U> for T where U: TryFrom<T>, {
type Error = U::Error;
fn try_into(self) -> Result<U, U::Error> {
U::try_from(self)
}
}
如果我们把 T
和 U
分别换成 Big
和 Small
的话,代码就变成了:
rust
impl<Big, Small> TryInto<Small> for Big where Small: TryFrom<Big>, {
type Error = Small::Error;
fn try_into(self) -> Result<Small, Small::Error> {
Small::try_from(self)
}
}
上述代码非常清晰明了,代码为 Big
实现了 TryInto<Small>
,同时约束了 Small
必须实现 TryFrom<Big>
,由于我们已经为 Small
实现了 TryFrom<Big>
,所以满足此处的约束,编译器将自动为我们实现 TryInto<Small>
。
try_into
函数的实现同样非常简单,直接调用的就是 Small::try_from(self)
。
never
类型
!
类型被称为 never
类型,它是一个特殊的类型,表示一个值永远不会存在。
!
类型主要用于以下几种情况:
函数从不返回:
如果一个函数永远不会返回。例如,它总是调用 panic!
、loop
无限循环、或者调用 std::process::exit
等,那么它的返回类型可以被标记为 !
。
rust
fn panic_forever() -> ! {
panic!("This function will never return!");
}
fn loop_forever() -> ! {
loop {}
}
fn exit_forever() -> ! {
std::process::exit(0);
}
控制流中的类型推断:
!
类型在控制流中可以帮助类型推断。例如,在一个 match
表达式中,如果某个分支永远不会被执行(例如,它会导致程序崩溃或无限循环),那么该分支的类型可以被推断为 !
。
rust
fn example(x: Option<i32>) -> i32 {
match x {
Some(val) => val,
None => panic!("This should never happen!"),
}
}
在这个例子中,None
分支的类型是 !
,因为它会导致程序崩溃,永远不会返回一个值。
作为类型转换的目标:
!
类型可以被转换为任何其他类型。这是因为 !
类型表示:永远不会存在。所以它可以被视为任何类型的子类型。
rust
fn example() -> ! {
loop {}
}
fn example2() -> i32 {
example()
}
在这个例子中,函数 example
的返回类型是 !
,但它可以被转换为 i32
,因为 !
类型可以被视为任何类型的子类型。
Infallible
Infallible
这个 enum 源自于 std::convert
模块。它用来表示错误永远不会发生的错误类型。
听起来绕极了,我们先看它的实现代码:
rust
enum Infallible {}
由于该枚举没有成员,因此该类型的值实际上永远不可能存在。
这对于使用 Result
并参数化错误类型的泛型 API 非常有用,以表明结果始终是 Ok
,永远不会返回 Err
。
该枚举与 never
类型 !
具有相同的作用,但是 !
类型还没有完全稳定,Infallible
枚举目前也没有被 !
类型取代,因此 Infallible
与 !
类型目前只是作用相同,类型不同的两种东西。
自反性
TryFrom
和 TryInto
都具有自反性,也就是为 T
实现 TryFrom<T>
和 TryInto<T>
。我们来看标准库的实现:
rust
impl<T, U> TryFrom<U> for T where U: Into<T>, {
type Error = Infallible;
fn try_from(value: U) -> Result<Self, Self::Error> {
Ok(U::into(value))
}
}
从上面的代码中,我们看到 Error
是 Infallible
,Self::Error
等于 Error
就是 Infallible
。也就是说,try_from
函数的返回类型是 Result<Self, Infallible>
,表示错误永远不会发生。
同时我们还能了解到,上面的实现代码是对自反性的扩展,因为 U
可以就是 T
,也可以超越 T
,只需要保证 U
可以转换为 T
即可。
rust
impl<T> TryFrom<T> for T where T: Into<T>, {
type Error = Infallible;
fn try_from(value: T) -> Result<Self, Self::Error> {
Ok(T::into(value))
}
}
因为 From
和 Into
都具有自反性,也就是为 T
实现了 From<T>
和 Into<T>
,所以上面代码天然可行。
对于 TryInto
,则有如下实现:
rust
impl<T, U> TryInto<U> for T where U: TryFrom<T>, {
type Error = U::Error;
fn try_into(self) -> Result<U, U::Error> {
U::try_from(self)
}
}
此时,TryInto
的 U
对应于 TryFrom
的 T
,TryInto
的 T
对应于 TryFrom
的 U
,T U
转换后即是如下:
rust
impl<T, U> TryInto<T> for U where T: TryFrom<U>, {
type Error = T::Error;
fn try_into(self) -> Result<T, T::Error> {
T::try_from(self)
}
}
由此我们可以写出如下代码:
rust
struct Foo;
fn func1<T: TryFrom<T>>(_: T) {}
fn func2<T: TryInto<T>>(_: T) {}
fn main() {
func1::<bool>(true);
func1::<i32>(10_i32);
func1::<Foo>(Foo);
func2::<bool>(true);
func2::<i32>(10_i32);
func2::<Foo>(Foo);
}
以及:
rust
use std::convert::Infallible;
struct Foo;
fn main() {
let _: Result<bool, Infallible> = bool::try_from(true);
let _: Result<bool, Infallible> = true.try_into();
let _: Result<i32, Infallible> = i32::try_from(42);
let _: Result<i32, Infallible> = 42.try_into();
let _: Result<Foo, _> = Foo::try_from(Foo);
let _: Result<Foo, _> = Foo.try_into();
}
结语
参考来自 Rust 1.82.0
版本。