「Rust 中方便和习惯性的转换」part1

341 阅读3分钟

「这是我参与11月更文挑战的第 19 天,活动详情查看:2021最后一次更文挑战


我们经常将数据从一种表示转换为另一种表示。有如下几种情况下会出现这种需求:

  • 将大量的类型转换为更方便的类型
  • 将"外来"的错误类型转换为我们库中的错误类型
  • 以及对我们自定义协议的网络数据包进行编码和解码

第一种情况可能是最常见的。例如,在某些情况下,普通的 Vec<T> 是一种方便的表示方法,所以有现成的方法可以将其他类型的值如: VecDeque<T>/BinaryHeap<T>/&[T]/&str,转换成Vec<T>

当然,在Rust中转换类型的方法不止一种,每种方法都有其优点和缺点。我们可以:

  • 使用 struct 建立自己的目标类型,但这样做很乏味重复,而且会暴露实现细节
  • 为每个源类型创建专门的构造函数(例如:new_from_vec_dequenew_from_binary_heap, new_from_slice),但这同样麻烦,而且我们可能会丢掉某些情况。
  • 编写接受某种trait的通用构造函数,但可能有些情况并没有提供,而且还需要额外构造函数。
  • 将枚举值转换为整数,反之亦然,但这些转换可能会产生意想不到的结果。
  • ...

然后你会明白:有无数种转换类型的方法。但其中很多都很糟糕,不过一定会有更好的方法!

在这篇文章中,我们将探讨如何以一种更习惯的方式来做这件事。std::convert 中的trait:FromIntoTryFromTryIntoAsRefAsMut

有这个确切的目的。这些特性为类型转换提供了一个统一的API,我们将探索如何利用它们来实现一个一致的、符合人体工程学的API。一旦你了解了它们,你就会开始注意到它们在文档中随处可见。我希望,在本文结束时,你可能会像我一样欣赏它们。

From and Into

From表示将一个T类型的值转换为一个目标类型(impl From<T> for TargetType)。这种转换可能是也可能不是很昂贵的计算,但我们通常可以认为它并不轻便。让我们看一下它的定义:

#[stable(feature = "rust1", since = "1.0.0")]
pub trait From<T>: Sized {
    /// Performs the conversion.
    #[stable(feature = "rust1", since = "1.0.0")]
    fn from(T) -> Self;
}

From包含一个方法签名:from(),我们必须实现它来执行实际转换逻辑。通过检查from()的签名,我们可以知道它移动(或者说是消耗)了参数。它的返回值Self也让我们知道转换可能不会失败。在这篇文章的后面,我们将研究 TryFrom 对可能失败的转换。From也是一个自反trait,这意味着将一个值转换为它自己的类型(From<T> for T)是可以实现的,并且不修改的情况下返回参数。

继续往下看,我们来到了 From 的对称trait:[Into<T>](<https://doc.rust-lang.org/std/convert/trait.Into.html>)。和From一样,Into也有个简短定义。

#[stable(feature = "rust1", since = "1.0.0")]
pub trait Into<T>: Sized {
    /// Performs the conversion.
    #[stable(feature = "rust1", since = "1.0.0")]
    fn into(self) -> T;
}

正如我们在定义中看到的,Into::into()消耗self并返回T,与From::from()相反,它消耗一个参数T并返回Self。比较一下这两种转换值的方式:

// `from` can be called from either the `From` trait or the target type.
// Calling from the target type makes our intention clearer.
let converted_value = From::from(original_value);
let converted_value = TargetType::from(original_value);

// `into` is usually called directly on the original value, but we can
// also call it from the Into trait or the source type:
let converted_value = original_value.into();
let converted_value = Into::into(original_value);