更多解包方法

我们已经知道,标准库提供了Result 和 Option 两个泛型枚举类型,也已经知道,枚举类型可以通过使用 if let Some()/Err()/Ok() 或者是 match 语法对结果进行匹配处理,如果仅仅如此让我们看一个之前的代码

下面代码是我们开发中经常需要处理的情况,判断函数返回值,然后对不同返回情况做出不一样的处理

use std::path::Path;
use std::io::prelude::*;
use std::fs::File;


fn read_file_content(filename:&str) -> Result<String,String> {
    let path = Path::new(filename);
    let mut file = match File::open(&path) {
        Ok(file) => file,
        Err(err) => return Err(format!("{}",err)),
    };

    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(err) => Err(format!("{}",err)),
    }
}

fn main() {
    match read_file_content("a.txt") {
        Ok(content) => println!("read content: {}",content),
        Err(err) => println!("read content err: {} ",err),
    }
}

可以看到,对于 枚举类型的错误类型 处理起来有几个问题

  • 错误和值被包装了一层,实际在使用时,往往都是需要先解压在使用,而解压动作必须增加一个block
  • 错误和正确的情况一般情况下都需要分开处理,由于错误处理的代码占据函数越多空间,则代码可读性越差;

为了解决上述两个问题,RUST提供了更多的方法处理Option 和Result

map&map_err

map用于封装类型转换的函数

option的map 定义如下:

pub fn map<U, F>(self, f: F) -> Option<U>
where
    F: FnOnce(T) -> U,
{
    match self {
        Some(x) => Some(f(x)),
        None => None,
    }
}

从map的实现上来开,有几个泛型需要注意:

  • F:代表一个函数类型,该函数接收一个 泛型T,然后输出泛型U
  • T:代表 Some包裹的类型
  • U: 代表函数输出的类型

result的map 定义如下: 和option类似,只处理Ok的情况

pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
    match self {
        Ok(t) => Ok(op(t)),
        Err(e) => Err(e),
    }
}

当然也还有针对Err情况的Map(仅针对Result类型)

pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F> {
    match self {
        Ok(t) => Ok(t),
        Err(e) => Err(op(e)),
    }
}

那么 我们一开始的代码可以修改为:

use std::path::Path;
use std::io::prelude::*;
use std::fs::File;

fn err_to_string(err: std::io::Error) -> String {
    format!("{}",err)
}

fn read_file_content(filename:&str) -> Result<String,String> {
    let path = Path::new(filename);
    let mut file = match File::open(&path).map_err(err_to_string) {
        Ok(file) => file,
        Err(err) => return Err(err),
    };

    let mut s = String::new();
    match file.read_to_string(&mut s).map_err(err_to_string) {
        Ok(_) => Ok(s),
        Err(err) => Err(err),
    }
}

fn main() {
    match read_file_content("a.txt") {
        Ok(content) => println!("read content: {}",content),
        Err(err) => println!("read content err: {} ",err),
    }
}

map_or&map_or_else

map_ormap_or_else 用于直接解压,并且对内部类型进行归一,需要传递一个默认值

我们知道 option 和 Result 如果解压 会有可能得到两个不同的类型,但是有时,我们可能会让程序正常执行;然后给错误类型一个 默认值,这两个方法用于完成这个动作,同时还支持传入闭包或者函数 对解压值进行转换

pub fn map_or<U, F>(self, default: U, f: F) -> U
where
    F: FnOnce(T) -> U,
{
    match self {
        Some(t) => f(t),
        None => default,
    }
}

pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
    match self {
        Ok(t) => f(t),
        Err(_) => default,
    }
}


pub fn map_or_else<U, D, F>(self, default: D, f: F) -> U
where
    D: FnOnce() -> U,
    F: FnOnce(T) -> U,
{
    match self {
        Some(t) => f(t),
        None => default(),
    }
}

pub fn map_or_else<U, D: FnOnce(E) -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
    match self {
        Ok(t) => f(t),
        Err(e) => default(e),
    }
}

and&and_then&or&or_esle

结果如果表示成功或者失败,利用 andor 可以对结果进行类似 条件运算 比如 true & false; true or false

and 的定义和使用 Option类似 不做演示

pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> {
    match self {
        Ok(_) => res,
        Err(e) => Err(e),
    }
}


let x: Result<u32, &str> = Ok(2);
let y: Result<&str, &str> = Err("late error");
assert_eq!(x.and(y), Err("late error")); // Ok 和 Err and 运算之后得到 Err

let x: Result<u32, &str> = Err("early error");
let y: Result<&str, &str> = Ok("foo");
assert_eq!(x.and(y), Err("early error")); // Err 和 OK运算之后得到Err


let x: Result<u32, &str> = Err("not a 2");
let y: Result<&str, &str> = Err("late error");
assert_eq!(x.and(y), Err("not a 2")); // Err 和Err 运算之后 得到的是 self的Err

let x: Result<u32, &str> = Ok(2);
let y: Result<&str, &str> = Ok("different result type");
assert_eq!(x.and(y), Ok("different result type")); // OK 和 Ok运算之后 得到的是 y的Ok

and_then 的定义和使用: and_then 支持在得到结果后 进一步对结果进行运算 增加了and运算的map

pub fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E> {
    match self {
        Ok(t) => op(t),
        Err(e) => Err(e),
    }
}

pub fn and_then<U, F>(self, f: F) -> Option<U>
where
    F: FnOnce(T) -> Option<U>,
{
    match self {
        Some(x) => f(x),
        None => None,
    }
}

其他方法

类型库还提供了 unwarp/unwrap_or/unwrap_or_esle 总之 当你觉得 Result 和Option造成了代码长度增加,请尝试去 标准库找找是否有对应的方法可以优化代码

as_ref&as_mut&take

我们在上面看到的所有解包,请你仔细观察他的self 入参,Option不支持Copy语义 意味着 每次解包等于消耗了Option自身

有时我们并不希望消耗掉Option 我们希望使用他的引用 比如下面的示例

#[derive(Debug)]
struct Node {
    next: Option<Box<Node>>,
}


fn main() {
    let node1 = Node {
        next: None,
    };
    let node_head = Node {next: Some(Box::new(node1))};

    //print node_head next
    println!("next: {:?} ", node_head.next.unwrap());

    //what happended
    println!("next: {:?} ", node_head.next.expect("no content"));
}

有时我们也真的希望移动 Option的值 但是又不希望破环掉Option

#[derive(Debug)]
struct List {
    head: Option<Box<Node>>,
}

#[derive(Debug)]
struct Node {
    data: i32,
    next: Option<Box<Node>>,
}

impl List {
    fn new() -> List {
        List{head:None}
    }

    //模拟入栈
    fn push(&mut self, data: i32)  {
        let mut node = Node {next: None, data: data };
        match self.head {
            None => self.head = Some(Box::new(node)),
            Some(_) => {
                node.next = self.head.take();
                self.head = Some(Box::new(node));
            }
        }
    }

    fn show(&self) {
        let mut cur_ref = self.head.as_ref();
        loop {
            match cur_ref {
                Some(box_ref) =>  {
                    println!("data:{}",box_ref.as_ref().data);
                    cur_ref = box_ref.next.as_ref();
                    continue;
                }
                None =>  break,
            }
        }
    }


}

fn main() {
    let mut li = List::new();
    li.push(3);
    li.push(2);
    li.push(1);

    li.show();
    li.show();
}

ok&ok_or

Option 可以通过ok_or ok_or_else方法 转为 Result 类型

pub fn ok_or<E>(self, err: E) -> Result<T, E> {
    match self {
        Some(v) => Ok(v),
        None => Err(err),
    }
}

   pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
    where
        F: FnOnce() -> E,
    {
        match self {
            Some(v) => Ok(v),
            None => Err(err()),
        }
    }

result 可以通过ok err 方法 转为 Option 类型

pub fn ok(self) -> Option<T> {
    match self {
        Ok(x) => Some(x),
        Err(_) => None,
    }
}

及早返回&

及早返回有点像 java的异常透传,我们在解包时,如果返回正确则解包,否则直接把错误类型透传到上一层 提前返回

我们一开始的代码可以这样修改

use std::path::Path;
use std::io::prelude::*;
use std::fs::File;

fn err_to_string(err: std::io::Error) -> String {
    format!("{}",err)
}

fn read_file_content(filename:&str) -> Result<String,String> {
    let path = Path::new(filename);
    let mut file =  File::open(&path).map_err(err_to_string)?;

    let mut s = String::new();
    file.read_to_string(&mut s).map_err(err_to_string)?;
    Ok(s)
}

fn main() {
    match read_file_content("a.txt") {
        Ok(content) => println!("read content: {}",content),
        Err(err) => println!("read content err: {} ",err),
    }
}