rust语言基础学习: 使用From和TryFrom trait进行类型之间的转换

rust语言基础学习: 使用From和TryFrom trait进行类型之间的转换

2020-07-20
Rust

昨天学习了Rust中trait的基础知识,今天开始将学习由Rust标准库中提供的经常会用到的trait。

在之前我们已经学习过了FromStr, Deref, DerefMut这3个trait,这里先做一个复习。

1.复习FromStr, Deref, DerefMut trait #

std::str::FromStr用于字符串slice str到其他类型的转换,一个名称为Point的类型实现了FromStr trait后,调用Point::from_str()就能完成从str到Point的转换,也可以使用str.parse()隐式调用的形式。 需要注意FromStr没有生命周期参数,因此只能解析本身不包含生命周期的类型。

Deref用于不可变引用的解引用操作,如*v。 实现Deref trait允许我们重载不可变引用的解引用运算符*。 实现了Deref trait的智能指针可以被当做常规引用来对待,以便于访问其智能指针背后的数据。

DerefMut用于可变引用的解引用操作,如*v = 1;。 实现DerefMut trait允许我们重载可变应用的解引用运算符*。 实现了DerefMut trait的智能指针可以被当做常规可变引用来对待。

Rust为了提高在函数或方法传参时的便利性,提供了 Deref强制转换(Deref coercion) 功能,使得Rust程序员编写函数和方法调用代码时时无需增加过多的显式使用&*的引用和解引用。 简单理解,如果有一个函数签名是fn foo(s: &str),就可以使用Box<String>的引用传参调用foo函数,因为智能指针Box和String都实现了Deref,传参的Box引用会经过两次deref,自动完成参数传递的类型转换:

1// 使用Deref强制转换
2let x = Box::new(String::from("hello"));
3foo(&x); // Deref coercion
4
5// 不使用Deref强制转换
6let x = Box::new(String::from("hello"));
7let s: String = *x; // 将Box<String>解引用为String
8let s: &str = &s[..]; // 使用`&`和`[..]`获取整个String的字符串切片
9hello(s);

因为DerefMut tait继承了Deref trait,所以DerefMut也具有"Deref强制转换功能”。

接下来学习From, TryFrom这两个trait,它们约定了类型之间如何转换的行为。

2.From trait和Into trait #

std::convert::From trait的定义如下:

1pub trait From<T> {
2    fn from(T) -> Self;
3}

如果一个类型实现了From<T>,则可以使用from实现从输入的T转换到该类型。

Into trait描述了与From trait相反操作的行为,如果一个类型实现Into<T>,则使用into可以实现从该类型到T类型的转换。

Into trait的定义如下:

1pub trait Into<T> {
2    fn into(self) -> T;
3}

一般我们只为类型实现From trait而不是Into trait,实现From trait会自动为From<T>中的T实现Into trait,这是因为标准库中提供了下面自动实现的代码:

 1// From implies Into
 2#[stable(feature = "rust1", since = "1.0.0")]
 3impl<T, U> Into<U> for T
 4where
 5    U: From<T>,
 6{
 7    fn into(self) -> U {
 8        U::from(self)
 9    }
10}

从上面的代码可以看出,rust的泛型的抽象能力太强大了。

下面来看一个例子,因为String实现了From<&str>,所以&str自动实现了Into<String>。所以从&strString的转换以下两种方式都可以:

1let s = String::from("hello");
2let s: String = "hello".into();

3.From trait简化错误处理 #

除了进行类型转换,From trait还能用于简化错误处理。当在编写可能会失败的函数时,其返回类型通常是Result<T, E>的形式。 通过实现From trait,可以允许一个函数返回一个封装了多种错误类型的单一错误类型。还是看具体的例子吧。 这里以rustlings中的exercises/advanced_errors/advanced_errs1.rs为例。

定义一个只有一个字段0的元组结构体PositiveNonzeroInteger,用来描述所有的正整数,再给PositiveNonzeroInteger编写一个new函数,返回的是Result<PositiveNonzeroInteger, CreationError>。 new函数的参数是i64,从i64创建正整数类型PositiveNonzeroInteger,如果参数是负数或零时,应该返回CreationError,CreationError是个枚举,包含Negative和Zero。

 1#[derive(PartialEq, Debug)]
 2struct PositiveNonzeroInteger(u64);
 3
 4#[derive(PartialEq, Debug)]
 5enum CreationError {
 6    Negative,
 7    Zero,
 8}
 9
10impl PositiveNonzeroInteger {
11    fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
12        match value {
13            x if x < 0 => Err(CreationError::Negative),
14            x if x == 0 => Err(CreationError::Zero),
15            x => Ok(PositiveNonzeroInteger(x as u64)),
16        }
17    }
18}

接下来接到实现str.parse()到PositiveNonzeroInteger的需求,实现这个需求就是要为PositiveNonzeroInteger实现FromStr trait。 实现FromStr的话,需要设置FromStr内部的关联类型Err, 同时实现from_str函数,于是有了下面第1版实现:

 1use std::num::ParseIntError;
 2use std::str::FromStr;
 3
 4#[derive(PartialEq, Debug)]
 5struct PositiveNonzeroInteger(u64);
 6
 7#[derive(PartialEq, Debug)]
 8enum CreationError {
 9    Negative,
10    Zero,
11}
12
13impl PositiveNonzeroInteger {
14    fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
15        match value {
16            x if x < 0 => Err(CreationError::Negative),
17            x if x == 0 => Err(CreationError::Zero),
18            x => Ok(PositiveNonzeroInteger(x as u64)),
19        }
20    }
21}
22
23#[derive(PartialEq, Debug)]
24enum ParsePosNonzeroError {
25    Creation(CreationError),
26    ParseInt(ParseIntError),
27}
28
29impl FromStr for PositiveNonzeroInteger {
30    type Err = ParsePosNonzeroError;
31    fn from_str(s: &str) -> Result<PositiveNonzeroInteger, Self::Err> {
32        let x = s.parse();
33        if x.is_err() {
34            return Err(ParsePosNonzeroError::ParseInt(x.err().unwrap()));
35        }
36        let r = PositiveNonzeroInteger::new(x.unwrap());
37        match r {
38            Ok(i) => Ok(i),
39            Err(e) => Err(ParsePosNonzeroError::Creation(e)),
40        }
41        
42    }
43}
44
45fn main() {
46    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "11".parse();
47    println!("{:?}", n); // Ok(PositiveNonzeroInteger(11))
48    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "-11".parse();
49    println!("{:?}", n); // Err(Creation(Negative))
50    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "0".parse();
51    println!("{:?}", n); // Err(Creation(Zero))
52    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "1.0".parse();
53    println!("{:?}", n); // Err(ParseInt(ParseIntError { kind: InvalidDigit }))
54}

上面的代码从from_str函数的错误处理使用了最原始的手动做错误类型转换的再传播错误的方式,错误处理的代码比较多,和业务代码混在一起也不易于阅读。

能否使用?操作符来自动将可能出现的CreationError和ParseIntError传播到上游调用者呢?但是因为from_str可能返回的错误时ParsePosNonzeroError,也就是上游调用者使用的错误时ParsePosNonzeroError。 ?操作符默认是不支持下游到上游错误类型不一致时的错误传播的,不支持CreationError(和ParseIntError)到ParsePosNonzeroError的错误传播。下面尝试使用?进行错误传播的代码无法编译通过:

 1use std::num::ParseIntError;
 2use std::str::FromStr;
 3
 4...
 5
 6impl FromStr for PositiveNonzeroInteger {
 7    type Err = ParsePosNonzeroError;
 8    fn from_str(s: &str) -> Result<PositiveNonzeroInteger, Self::Err> {
 9        let x = s.parse()?; // expected struct `ParseIntError`, found enum `ParsePosNonzeroError`
10        Ok(PositiveNonzeroInteger::new(x)?) // the trait `From<CreationError>` is not implemented for `ParsePosNonzeroError`
11    }
12}
13
14...

编译错误的提示信息是让我们为上游错误类型实现From<下游错误类型> trait。实际上也是这样的。 From trait简化错误处理,就是在进行错误传播当上下游错误类型不一致时,通过实现From trait,使用?操作符自动实现下游到上游的错误转换。

最终版本的代码如下:

 1use std::num::ParseIntError;
 2use std::str::FromStr;
 3
 4#[derive(PartialEq, Debug)]
 5struct PositiveNonzeroInteger(u64);
 6
 7#[derive(PartialEq, Debug)]
 8enum CreationError {
 9    Negative,
10    Zero,
11}
12
13impl PositiveNonzeroInteger {
14    fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
15        match value {
16            x if x < 0 => Err(CreationError::Negative),
17            x if x == 0 => Err(CreationError::Zero),
18            x => Ok(PositiveNonzeroInteger(x as u64)),
19        }
20    }
21}
22
23#[derive(PartialEq, Debug)]
24enum ParsePosNonzeroError {
25    Creation(CreationError),
26    ParseInt(ParseIntError),
27}
28
29impl From<CreationError> for ParsePosNonzeroError {
30    fn from(e: CreationError) -> Self {
31        ParsePosNonzeroError::Creation(e)
32    }
33}
34
35impl From<ParseIntError> for ParsePosNonzeroError {
36    fn from(e: ParseIntError) -> Self {
37        ParsePosNonzeroError::ParseInt(e)
38    }
39}
40
41impl FromStr for PositiveNonzeroInteger {
42    type Err = ParsePosNonzeroError;
43    fn from_str(s: &str) -> Result<PositiveNonzeroInteger, Self::Err> {
44        let x = s.parse()?;
45        Ok(PositiveNonzeroInteger::new(x)?)
46    }
47}
48
49fn main() {
50    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "11".parse();
51    println!("{:?}", n); // Ok(PositiveNonzeroInteger(11))
52    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "-11".parse();
53    println!("{:?}", n); // Err(Creation(Negative))
54    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "0".parse();
55    println!("{:?}", n); // Err(Creation(Zero))
56    let n: Result<PositiveNonzeroInteger, ParsePosNonzeroError> = "1.0".parse();
57    println!("{:?}", n); // Err(ParseInt(ParseIntError { kind: InvalidDigit }))
58}

上面代码2939行,上游错误ParsePosNonzeroError分别实现了From<CreationError>和From<ParseIntError> trait。这样在4346行from_str函数的实现中,如果有错误发生时,?操作符会自动转换上下游错误的类型,进行错误传播。

?运算符所使用的错误值被传递给了From trait约定的from函数,其用来将错误从一种类型转换为另一种类型。当?运算符调用from函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。 这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了from函数来定义如何将自身转换为返回的错误类型,?运算符会自动处理这些转换。

4.TryFrom和TryInto trait #

类似于From和Into,TryFrom和TryInto适用于易出错的转换场景,其返回值是Result类型。

它们的定义如下:

 1pub trait TryFrom<T> {
 2    type Error;
 3    fn try_from(value: T) -> Result<Self, Self::Error>;
 4}
 5
 6
 7pub trait TryInto<T> {
 8    type Error;
 9    fn try_into(self) -> Result<T, Self::Error>;
10}

可以看到TryFrom, TryInto对比From和Into,多了个关联类型Error,try_from和try_into函数的返回值是Result,可以返回转换时出现的错误。

参考 #

© 2024 青蛙小白