38、可处理错误和Result枚举

Fallibility(可错性)

 

重新回顾之前练习中的Ticket::new函数

impl Ticket {
    pub fn new(
        title: String, 
        description: String, 
        status: Status
    ) -> Ticket {
        if title.is_empty() {
            panic!("Title cannot be empty");
        }
        if title.len() > 50 {
            panic!("Title cannot be longer than 50 bytes");
        }
        if description.is_empty() {
            panic!("Description cannot be empty");
        }
        if description.len() > 500 {
            panic!("Description cannot be longer than 500 bytes");
        }

        Ticket {
            title,
            description,
            status,
        }
    }
}

 

一旦其中一项检查失败,函数就会panic。这种情况并不理想,因为它不给调用者处理错误的机会。

 

Result赶紧端上来吧,这是Rust处理错误的主要机制。

 

Result类型

 

Result是在标准库中定义的枚举类型。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

 

它有两种变体:

  • Ok(T)表示操作成功,它保存操作的输出T
  • Err(E)表示操作失败,它保存E,即发生的错误。

 

OkErr都是通用类型,允许我们分别为成功和错误的情况指定我们自己的类型。

 

无异常机制

 

Rust中的可恢复错误用数值表示。

它们只是一种类型的实例,像任何其他值一样被传递和操作。这与其他语言(比如Python或C#)有显著的差别。在其他语言中,异常用于发出错误信号。

 

异常会创建一个难以推理的单独控制流路径。光看函数的签名,我们不知道它是否能抛出异常。光看函数的签名,我们也不知道它能抛出那些异常类型。

这必须阅读函数的文档或查看其实现才能知道。

 

异常处理逻辑的局部性很差:抛出异常的代码与捕获异常的代码相差甚远,两者之间没有直接联系。

 

Fallibility(可错性)被编码在类型系统中

 

使用Result,Rust会强迫我们再函数签名中对可错性进行编码。

如果函数可能失败(而且我们希望调用者有处理异常的机会),那么它必须返回一个结果。

 

// Just by looking at the signature, you know that this function 
// can fail. You can also inspect `ParseIntError` to see what 
// kind of failures to expect.
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
    // ...
}

 

这就是Result的最大优势:它让可错性变得明确。

 

但是要记住,panic是存在的。他们不会被类型系统所追踪,就像其它语言中的异常一样。但它们是针对不可恢复的错误的,应该尽量少用。

 

练习

 

这两节的练习,确实让人身心愉悦,我们直接看错误处理的题目:

// TODO: Convert the `Ticket::new` method to return a `Result` instead of panicking.
//   Use `String` as the error type.

#[derive(Debug, PartialEq)]
pub struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq)]
pub enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> /* TODO */ {
        if title.is_empty() {
            return Err("Title cannot be empty".into());
        }
        if title.len() > 50 {
            return Err("Title cannot be longer than 50 bytes".into());
        }
        if description.is_empty() {
            return Err("Description cannot be empty".into());
        }
        if description.len() > 500 {
            return Err("Description cannot be longer than 500 bytes".into());
        }

        /* TODO */
    }
}

 

答案:

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Result<Ticket, String> {
        if title.is_empty() {
            return Err("Title cannot be empty".into());
        }
        if title.len() > 50 {
            return Err("Title cannot be longer than 50 bytes".into());
        }
        if description.is_empty() {
            return Err("Description cannot be empty".into());
        }
        if description.len() > 500 {
            return Err("Description cannot be longer than 500 bytes".into());
        }

        Ok(Ticket{
            title,
            description,
            status,
        })
    }
}

 

感觉还可以,没前几天那么高难度了,其实我之前也看过这方面的书,也就很好理解了。

 

这一节的学习就先到这里了。

阅读剩余
THE END