45、TryFrom Trait
TryFrom
和TryInto
上一个章节我们学到了From
trait和Into
trait,这是Rust的用于无误类型转换的惯用接口。
但是,如果不能保证转换一定成功该怎么办呢?
现在我们对错误有了足够的了解,可以讨论From
和Into
能够进行错误处理的版本:TryFrom
和TryInto
。
TryFrom
和TryInto
如同From
和Into
,TryFrom
和TryInto
都在std::convert
中定义。
pub trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
From
/Into
and TryFrom
/TryInto
的主要区别在于后者返回的是Result
类型。
这样就允许转换失败,返回一个错误而不是panic。
Self::Error
TryFrom
和TryInto
类型都有关联的Error
类型。这使得每种实现可以指定自己的错误类型,理想情况下应为所执行转换最合适的错误类型。
Self::Error
是一种引用trait本身定义的Error
关联类型的方法。
Duality(对偶性)
就像From
和Into
一样,TryFrom
和TryInto
也是dual traits。如果我们为某个类型实现了TryFrom
,我们也可以直接获得TryInto
。
练习
累了,这次的联系直接看答案吧还是(
原题:
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for `Status`.
// The parsing should be case-insensitive.
#[derive(Debug, PartialEq, Clone)]
pub enum Status {
ToDo,
InProgress,
Done,
}
/* TODO */
/* TODO */
/* TODO */
观察题目,题目想让我们定义一个错误枚举类型,然后实现TryFrom
和TryInto
这两个trait,并且能够正确处理错误。
我们看答案:
// TODO: Implement `TryFrom<String>` and `TryFrom<&str>` for `Status`.
// The parsing should be case-insensitive.
#[derive(Debug, PartialEq, Clone)]
pub enum Status {
ToDo,
InProgress,
Done,
}
#[derive(Debug, thiserror::Error)]
#[error("{invalid_status} is not a valid status")]
pub struct ParseStatusError {
invalid_status: String,
}
impl TryFrom<String> for Status {
type Error = ParseStatusError;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
impl TryFrom<&str> for Status {
type Error = ParseStatusError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"todo" => Ok(Status::ToDo),
"inprogress" => Ok(Status::InProgress),
"done" => Ok(Status::Done),
_ => Err(ParseStatusError {
invalid_status: value.to_string(),
}),
}
}
}
这段代码是定义错误枚举类型
#[derive(Debug, thiserror::Error)]
#[error("{invalid_status} is not a valid status")]
pub struct ParseStatusError {
invalid_status: String,
}
实现了Debug
和thiserror::Error
这两个派生宏。同时设定了error的错误信息,囊括了结构体里面的invalid_status
成员。
这段代码是在实现<Status>.from(<String>),定义了一个关联类型Error
,关联类型就是刚刚我们自定义的错误枚举类型,然后实现try_from
方法,参数是String
类型的一个变量,返回值是Result
类型,这里直接调用了&str->Status
的Into方法,这里体现了对偶性。
impl TryFrom<String> for Status {
type Error = ParseStatusError;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
这段代码实现了真正的转换流程:
impl TryFrom<&str> for Status {
type Error = ParseStatusError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"todo" => Ok(Status::ToDo),
"inprogress" => Ok(Status::InProgress),
"done" => Ok(Status::Done),
_ => Err(ParseStatusError {
invalid_status: value.to_string(),
}),
}
}
}
唯一不同的地方是,方法体的内部实现不一样,因为题目说是转换都是小写的。于是match语句的后面先调用了to_lowercase()
再调用as_str()
,转换为了str
类型。
然后模式匹配,首先匹配三个Ok()
,直接返回Status对应的枚举变体。
最后一个通配符返回错误处理,这个错误结构体携带了错误的值,作为一个Self::Error
返回了,这是一个结构体,一个被Err()
包裹的错误类状态结构体。
不过这个Self::Error
是怎么和ParseStatusError
绑定的呢?
难道说,Self在这里不是Status?(其实就是Status…)
这一节的内容就先到这吧。