Rust标准库:TryFrom和TryInto

介绍

TryFromTryInto 两个 trait 均源自于 std::convert 模块,它们在类型转换当中扮演着重要角色。

  • TryFromTryInto 都会消耗原始类型的值(即获取其所有权),并将其转换为另一种类型的值,最终返回 Result 类型。

  • 应该始终优先实现 TryFrom 而不是 TryInto,因为实现 TryFrom 后会自动通过标准库中的通用实现提供对应 TryInto 的实现。

  • 为泛型函数指定 trait 约束时,优先使用 TryInto 而不是 TryFrom,这样对于只实现了 TryInto 而没有实现 TryFrom 的类型也可以作为参数来使用。

  • FromInto 不同,TryFromTryInto 允许类型转换出现失败,并且失败后能够优雅地返回错误。

定义

我们首先来看看 TryFromTryInto 的定义:

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 的类型。

也就是说 TryFromTryInto 只能被那些在编译时大小已知的类型所实现。

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)
    }
}

如果我们把 TU 分别换成 BigSmall 的话,代码就变成了:

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! 类型目前只是作用相同,类型不同的两种东西。

自反性

TryFromTryInto 都具有自反性,也就是为 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))
    }
}

从上面的代码中,我们看到 ErrorInfallibleSelf::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))
    }
}

因为 FromInto 都具有自反性,也就是为 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)
    }
}

此时,TryIntoU 对应于 TryFromTTryIntoT 对应于 TryFromUT 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 版本。

相关推荐
SomeB1oody11 小时前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
SomeB1oody1 天前
【Rust自学】4.2. 所有权规则、内存与分配
开发语言·后端·rust
SomeB1oody1 天前
【Rust自学】4.5. 切片(Slice)
开发语言·后端·rust
编码浪子2 天前
构建一个rust生产应用读书笔记6-拒绝无效订阅者02
开发语言·后端·rust
baiyu332 天前
1小时放弃Rust(1): Hello-World
rust
baiyu332 天前
1小时放弃Rust(2): 两数之和
rust
Source.Liu2 天前
数据特性库 前言
rust·cad·num-traits
编码浪子2 天前
构建一个rust生产应用读书笔记7-确认邮件1
数据库·rust·php
SomeB1oody2 天前
【Rust自学】3.6. 控制流:循环
开发语言·后端·rust
Andrew_Ryan2 天前
深入了解 Rust 核心开发团队:这些人如何塑造了世界上最安全的编程语言
rust