第9章 构建健壮的程序

 

本文简介

本文简介

9.1 通用概念

设计程序要考虑正常流程与非正常情况,我们对非正常情况分成三类:

  • 失败
    • 程序正常运行应具有的前提条件不对
  • 错误
    • 程序运行中遇到了预估到的问题
  • 异常
    • 程序运行中出现的各种没想到的问题

9.2 消除失败

Rust 用两种方法来消除失败:

  • 类型系统
  • 断言 Rust可以在编译期就对函数参数进行类型检查,消除类型不匹配的失败;对于那些在运行期才会出现的失败,我们使用断言应对。比如
    pub fn insert(&mut self, index: usize, element: T) {
    let len = self.len();
    assert!(index <= len);
    ...
    }
    
  • assert就是一种快速失败策略,尽早暴露问题。所以assert还可以输出自定义失败信息:assert!(x, "x不为真");
  • assert会占用CPU时间,所以可以使用debug_assert只在调试模式下起作用

程序运行的条件包括:

  • 前置条件
  • 后置条件
  • 前后不变

9.3 分层处理错误

Rust处理错误采用了分层处理的方式,应对工具是:

  • Option<T>:有无型错误
  • Result<T, E>:通用错误
  • panic!()
  • abort

9.3.1 有无型错误 Option

Option<T>包含Some(T)None两个变体。比如通过Option<T>方式找到最短名字的程序

fn main() {
  assert_eq!(show_shortest(vec!["Uku", "AiQi"]), "Uku");
  assert_eq!(show_shortest(Vec::new()), "Not Found");
}

这里show_shortest函数内部通过match来处理get_shotest函数返回的Option<&str>结果,我们可以认为show_shortest处理了有无型错误

fn show_shortest(names: Vec<&str>) -> &str {
  match get_shortest(names) {
    Some(shortest) => shortest,
    None => "Not Found",
  }
}

unwrap系方法

通过match操作Option<T>来得到内部的T在编程中太频率,所以Rust给出了一个语法糖,让处理过程更优雅,这就是:

  • unwrap():必须解包,不能就panic
  • expect("Not Found"):必须解包,不能就panic,但带自定义的信息
  • unwrap_or("Not Found"):解包或返回自定义结果
  • unwrap_or_else(|| "Not Found"):解包或执行闭包

map方法

通过match操作Option<T>来得到一个Option<U>的情况,在编译中也很频率,为此Rust给出了一个map的语法糖,这就是:

  • map(f):映射到函数里Option<T>.map(f) -> Option<f(T)>`
    • x.map(|v| v.len())
  • map_or(v, f):为None指定默认值v
    • x.map_or(52, |v| v.len())
  • map_or_else(g, f):为None指定默认函数g
    • x.map_or_else(|| 2*k, |v| v.len())

map(f)相当于:

match self {
  Some(x) => Some(f(x)),
  None => None,
}

比如:get_shortest(names).map(|name| name.len())

and_then方法

map(f).map(g).map(h)的一个隐含前提是f(x) -> T而不是f(x) -> Some(T),否则链式调用会变成Some(Some(Some(r)))。为了处理f(x)->Option<T>的情况,因为像log()这类的函数,其结果是可能为None的,所以它返回值是Option<T>类型,Rust给出了and_then()方法,它相当于:

match self {
  Some(x) => f(x),
  None => None,
}

也就是对于函数结果是Option的函数,使用and_then。比如inverse()double()都返回T,而log返回Option,则:

Option::from(number).map(inverse)
    .map(double).and_then(log)
    .map(square).and_then(sqrt)

9.3.2 通用的错误 Result<T, E>

Rust中Result<T, E>的定义:

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

Rust用对于Result类型的值,如果是Ok(value)正常的话不处理,有Err(ParseIntErr{kind: InvalidDigit})错误则引发panic,并且把Err(E)中的E输出

Rust对于Result类型,也有unwrap系方法,以及mapand_then的组合算子来处理。这里与Option<T>不同的是,Result多了一个Err(e),这个Err类型可以由我们自己在函数声明中指定,比如:

fn square(numstr: &str) -> Result<i32, ParseIntError> {
  numstr.parse::<i32>().map(|n| n.pow(2));
}

技巧,用type简化:

type ParseResult<T> = Result<T, ParseIntError>
fn square(numstr: &str) -> ParseResult<i32>;