Option和Result相关的组合算子

| 分类 Rust  | 标签 Rust 

前言

上一篇文章,从宏观的角度介绍了Rust错误处理的方法。提到了Option,作为弱化版的Result。

enum Option<T>{
	None,
	Some(T),
}

Option作为Rust的的系统类型,用来表示值不存在的可能。在编程中,这是一个非常好的做法,因为它强制Rust检测和处理值不存在的情况。

Option和Result,Rust都提供了一些组合算子来,来简化代码的书写,提供更强大的表达力。

Option相关的组合算子

ok_or和ok_or_else

这两个是一组,作用都是从Option转成Result。

pub fn ok_or<E>(self, err: E) -> Result<T, E>

pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
where
    F: FnOnce() -> E, 

其作用细细来讲,如下:

  • Some(v) –> Ok(v)
  • None –> Err(err)

其行为大概如下所示:

fn ok_or<T, E>(option: Option<T>, err: E) -> Result<T, E> {
    match option {
        Some(val) => Ok(val),
        None => Err(err),
    }
}

下面以一个例子来展示这个组合算子的作用

use std::env;

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
}

fn main() {
    match double_arg(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

如果执行,不给任何参数:

Error: Please give at least one argument

给一个参数为5,输出如下:

5

nth(1) 因为没有第一个参数(0-index),所以Option的值为None,ok_or将Option转成了Result,最终返回的Err(err)。

map

这个组合算子的作用是将Option 转换成Option

  • Some(T) ->Some(U)
  • None -> None
fn main() {
    let maybe_some_string = Some(String::from("Hello, World!"));
    let maybe_some_len = maybe_some_string.map(|s| s.len());

    assert_eq!(maybe_some_len, Some(13));
}

注意map是值传递,会消耗掉原始的值。

and_then

and_then 又被称为flatmap,它的作用是从一个Option转成另一Option:

  • None -> None
  • Some(x) -> Some(f(x))

对于Some而言,and_then把包裹的值作为参数

fn sq(x: u32) -> Option<u32> { Some(x * x) }
fn nope(_: u32) -> Option<u32> { None }

assert_eq!(Some(2).and_then(sq).and_then(sq), Some(16));
assert_eq!(Some(2).and_then(sq).and_then(nope), None);
assert_eq!(Some(2).and_then(nope).and_then(sq), None);
assert_eq!(None.and_then(sq).and_then(sq), None);

注意sq函数的入参,是u32型,并不是Some()。

Result相关的组合算子

is_ok and is_err

result.is_ok和result.is_err返回bool型的结果。

对于is_ok而言:

  • Ok(T) 返回true
  • Err(E) 返回false

对于is_err而言:

  • Ok(T)返回false
  • Err(E)返回true
let x: Result<i32, &str> = Err("Some error message");
assert_eq!(x.is_err(), true);

let x: Result<i32, &str> = Ok(-3);
assert_eq!(x.is_ok(), true);

ok and err

pub fn ok(self) -> Option<T>
pub fn err(self) -> Option<E>

注意,这两个算子都是将Result转成Option类型,一个关注成功的情况,一个关注出错的情况。

result.ok() 如果Result返回Ok(T),那么返回值是Option,即Some(success_value)。如果Result失败,那么该函数返回None,把Err的值丢弃掉。

let x: Result<u32, &str> = Ok(2);
assert_eq!(x.ok(), Some(2));

let x: Result<u32, &str> = Err("Nothing here");
assert_eq!(x.ok(), None);

result.err()如果Result失败,返回Err(E),那么返回返回Option

let x: Result<u32, &str> = Ok(2);
assert_eq!(x.err(), None);

let x: Result<u32, &str> = Err("Nothing here");
assert_eq!(x.err(), Some("Nothing here"));

and_then

Option也有and_then 算子,Result和Option一样的,语义是一样的。而且有很多这样的算子。

use std::env;

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
}

fn main() {
    match double_arg(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

还是前面的例子, ok_or()之后,Option就转成了Result,如果Ok的情况下,类型是字符串,那么and_then 会尝试将字符串解析成i32。

pub fn and_then<U, F>(self, op: F) -> Result<U, E>
where
    F: FnOnce(T) -> Result<U, E>, 

如果Result的值是Ok(T),那么执行op(T),否则直接返回Err。

n sq(x: u32) -> Result<u32, u32> { Ok(x * x) }
fn err(x: u32) -> Result<u32, u32> { Err(x) }

assert_eq!(Ok(2).and_then(sq).and_then(sq), Ok(16));
assert_eq!(Ok(2).and_then(sq).and_then(err), Err(4));
assert_eq!(Ok(2).and_then(err).and_then(sq), Err(2));
assert_eq!(Err(3).and_then(sq).and_then(sq), Err(3));

map and map_err

上面的例子中给出了map_err的使用。map和map_err是对Result中的Ok(T)或者Err(E)执行某个函数或者执行某种变换,得到新的Result:

pub fn map<U, F>(self, op: F) -> Result<U, E>
where
    F: FnOnce(T) -> U, 
    
pub fn map_err<F, O>(self, op: O) -> Result<T, F>
where
    O: FnOnce(E) -> F, 

对于map函数而言:

  • 如果Result值是Ok(T),对T执行函数
  • 如果Result的值是Err(E),保持不变,新的结果Option的值也是Err(E)

如下实例代码:

let line = "1\n2\n3\n4\n";

for num in line.lines() {
    match num.parse::<i32>().map(|i| i * 2) {
        Ok(n) => println!("{}", n),
        Err(..) => {}
    }
}

对于map_err而言:

  • 如果Result的值是Err(E),对E执行函数
  • 如果Result的值是Ok(T),保持不变。结果Result和输入Result一致。
fn stringify(x: u32) -> String { format!("error code: {}", x) }

let x: Result<u32, u32> = Ok(2);
assert_eq!(x.map_err(stringify), Ok(2));

let x: Result<u32, u32> = Err(13);
assert_eq!(x.map_err(stringify), Err("error code: 13".to_string()));

map_err可以用来对错误进行转换。

代码赏析

最后的最后,赏析一段Error Handle的代码

use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    File::open(file_path)
        .map_err(|err| err.to_string())
        .and_then(|mut file| {
            let mut contents = String::new();
            file.read_to_string(&mut contents)
                .map_err(|err| err.to_string())
                .map(|_| contents)
        })
    .and_then(|contents| {
        contents.trim().parse::<i32>()
            .map_err(|err| err.to_string())
    })
    .map(|n| 2 * n)
}

fn main() {
    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

上面这段代码中,有很多种不同的错误类型:

  • io::Error
  • num::ParseIntError

通过map_err都转成了Result<i32,String>类型。

上述写法看起来技巧性很强,但是我不喜欢,属于炫技派做法,心智负担比较重。

use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    let mut file = match File::open(file_path) {
        Ok(file) => file,
        Err(err) => return Err(err.to_string()),
    };
    let mut contents = String::new();
    if let Err(err) = file.read_to_string(&mut contents) {
        return Err(err.to_string());
    }
    let n: i32 = match contents.trim().parse() {
        Ok(n) => n,
        Err(err) => return Err(err.to_string()),
    };
    Ok(2 * n)
}

fn main() {
    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

这段代码,看起来更舒服,更易懂。


上一篇     下一篇