From
和 Into
两个 trait 均源自于 std::convert
模块,它们在类型转换当中扮演着重要角色。
From
和 Into
都会消耗原始类型的值(即获取其所有权),并将其转换为另一种类型的值,最终返回转换后的结果。
应该始终优先实现 From
而不是 Into
,因为实现 From
后会自动通过标准库中的通用实现提供对应 Into
的实现。
为泛型函数指定 trait 约束时,优先使用 Into
而不是 From
,这样对于只实现了 Into
而没有实现 From
的类型也可以作为参数来使用。
From
和 Into
在封装错误处理这块极为有用。
实现了 From
和 Into
的类型在进行类型转换时不允许失败,如果你的类型在转换时允许出现失败,请实现 TryFrom
和 TryInto
。
我们首先来看 From
和 Into
的定义:
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 的类型。
也就是说 From
和 Into
只能被那些在编译时大小已知的类型所实现。
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
到我们自定义类型 MyMutString
和 MyString
的转换。在此过程中,参考 MyMutString
对 From
的实现,我们甚至可以在转换时修改原始类型的值,然后再进行转换。
Into
自定义类型在实现 From
后,即可直接调用 into
函数,这是因为标准库自动帮我们实现了 Into
,我们来看看标准库是如何帮我们实现的:
impl<T, U> Into<U> for T where U: From<T>, {
fn into(self) -> U {
U::from(self)
}
}
如果我们把 T
和 U
分别换成 String
和 MyMutString
的话,代码就变成了:
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
呢还是其他什么类型呢?编译器犯糊涂了,那就选择果断报错吧:
按照编译器给出的指示,正确写法如下:
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());
}
From
和 Into
都具有自反性,也就是为 T
实现 From
和 Into
。我们来看标准库的实现:
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)
}