Rust 1.82.0 标准库:From和Into

介绍

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

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

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

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

  • FromInto 在封装错误处理这块极为有用。

  • 实现了 FromInto 的类型在进行类型转换时不允许失败,如果你的类型在转换时允许出现失败,请实现 TryFromTryInto

定义

我们首先来看 FromInto 的定义:

trait From<T>: Sized {
    fn from(value: T) -> Self;
}

trait Into<T>: Sized {
    fn into(self) -> T;
}

它们等价于下面的形式:

trait From<T> where Self: Sized {
    fn from(value: T) -> Self;
}

trait Into<T> where Self: Sized {
    fn into(self) -> T;
}

Self: Sized 是一种类型约束,用于表示当前的类型 Self 必须是一个在编译时大小已知的类型,即实现了 Sized trait 的类型。

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

trait Foo: Sized {
    fn func();
}

// ok
impl Foo for i32 {
    fn func() {}
}

// error
impl Foo for [i32] {
    fn func() {}
}

fn main() {
}

上述代码中,由于 [i32]DST 类型,编译期间大小未知,所以无法实现 Foo

示例

struct MyMutString {
    value: String,
}

struct MyString {
    value: String,
}

impl From<String> for MyMutString {
    fn from(mut value: String) -> Self {
        value.pop();
        Self {
            value,
        }
    }
}

impl From<String> for MyString {
    fn from(value: String) -> Self {
        Self {
            value,
        }
    }
}

fn main() {
    let string: String = "12345".to_string();

    let my_mut_string: MyMutString = MyMutString::from(string.clone());
    println!("{:?}", my_mut_string.value); // "1234"

    let my_mut_string: MyMutString = string.clone().into();
    println!("{:?}", my_mut_string.value); // "1234"

    let my_string: MyString = MyString::from(string.clone());
    println!("{:?}", my_string.value); // "12345"

    let my_string: MyString = string.clone().into();
    println!("{:?}", my_string.value); // "12345"
}

上述代码展示了如何实现从类型 String 到我们自定义类型 MyMutStringMyString 的转换。在此过程中,参考 MyMutStringFrom 的实现,我们甚至可以在转换时修改原始类型的值,然后再进行转换。

自动实现 Into

自定义类型在实现 From 后,即可直接调用 into 函数,这是因为标准库自动帮我们实现了 Into,我们来看看标准库是如何帮我们实现的:

impl<T, U> Into<U> for T where U: From<T>, {
    fn into(self) -> U {
        U::from(self)
    }
}

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

impl<String, MyMutString> Into<MyMutString> for String where MyMutString: From<String>, {
    fn into(self) -> MyMutString {
        MyMutString::from(self)
    }
}

上述代码非常清晰明了,代码为 String 实现了 Into,同时约束了 MyMutString 必须实现 From,由于我们已经为 MyMutString 实现了 From,所以满足此处的约束,编译器将自动为我们实现 Into

into 函数的实现同样非常简单,直接调用的就是 MyMutString::from(self)

指定类型

当我们调用 into 函数进行变量绑定时,必须明确指定类型。

struct Foo {}
struct Goo {}

impl From<String> for Foo {
    fn from(_: String) -> Self {
        Foo {}
    }
}

impl From<String> for Goo {
    fn from(_: String) -> Self {
        Goo {}
    }
}

fn main() {
    let string: String = String::from("string");
    let who1 = string.clone().into();
    let who2: _ = string.clone().into();
}

上述代码没有明确指定类型,编译器就没法确定 String 是应该转换成 Foo 呢还是 Goo 呢还是其他什么类型呢?编译器犯糊涂了,那就选择果断报错吧:

Rust 1.82.0 标准库:From和Into_第1张图片

按照编译器给出的指示,正确写法如下:

struct Foo {}
struct Goo {}

impl From<String> for Foo {
    fn from(_: String) -> Self {
        Foo {}
    }
}

impl From<String> for Goo {
    fn from(_: String) -> Self {
        Goo {}
    }
}

fn main() {
    let string: String = String::from("string");
    let foo: Foo = string.clone().into();
    let goo: _ = <String as Into<Goo>>::into(string.clone());
}

自反性

FromInto 都具有自反性,也就是为 T 实现 FromInto。我们来看标准库的实现:

impl<T> From<T> for T {
    fn from(t: T) -> T {
        t
    }
}

Into 会被标准库自动实现:

impl<T> Into<T> for T where T: From<T>, {
    fn into(self) -> T {
        T::from(self)
    }
}

由此我们可以写出如下代码:

struct Foo;

fn func1<T: From<T>>(_: T) {}
fn func2<T: Into<T>>(_: T) {}

fn main() {
    func1::<bool>(true);
    func1::<i32>(10_i32);
    func1::<Foo>(Foo);

    func2::<bool>(true);
    func2::<i32>(10_i32);
    func2::<Foo>(Foo);
}

以及:

struct Foo;

fn main() {
    let _: bool = bool::from(true);
    let _: bool = true.into();

    let _: i32 = i32::from(42);
    let _: i32 = 42.into();

    let _: Foo = Foo::from(Foo);
    let _: Foo = Foo.into();
}

封装错误处理

在进行错误处理时,为自己的自定义错误类型实现 From 通常很有用。

通过将底层错误类型转换为我们自己的自定义错误类型,我们可以在不丢失底层原因信息的情况下返回单一的错误类型。

? 操作符会自动使用 From::from 将底层错误类型转换为我们的自定义错误类型。

use std::fs;
use std::io;
use std::num;

enum CliError {
    IoError(io::Error),
    ParseError(num::ParseIntError),
}

impl From<io::Error> for CliError {
    fn from(error: io::Error) -> Self {
        CliError::IoError(error)
    }
}

impl From<num::ParseIntError> for CliError {
    fn from(error: num::ParseIntError) -> Self {
        CliError::ParseError(error)
    }
}

fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> {
    let mut contents = fs::read_to_string(&file_name)?;
    let num: i32 = contents.trim().parse()?;
    Ok(num)
}

你可能感兴趣的:(Rust标准库,rust)