有没有办法链接结果映射和解包?

Mar*_*ten 7 rust

我有这个代码.

if let Ok(file) = env::var("CONF") {
    if let Ok(mut reader) = fs::File::open(&file) {
        if let Ok(conf) = Json::from_reader(&mut reader) {
            // do something with conf
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我试图让它不像一个节日假日树,并考虑链接.注意,在这个链条的每一步产生另一种Result,所以显然这是不行的(我们得到ResultResult).

let conf = env::var("CONF")
    .map(fs::File::open)
    .map(Json::from_reader);

// do something with conf
Run Code Online (Sandbox Code Playgroud)

另外我的错误类型的每一步,这意味着我不能只更换不同.map使用.and_then.

我想我正在寻找类似于JavaScript承诺的东西.也就是说,从承诺内部返回的承诺揭开了内在的承诺.签名可能应该是这样的:

impl<T, E> Result<T, E> {
    fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D>
        where F: FnOnce(T) -> Result<U, D>
}
Run Code Online (Sandbox Code Playgroud)

Rust中有这样的机制吗?有没有另一种方法摆脱我的节日假日树?

She*_*ter 8

Rust中有这样的机制吗?

是的 - 虽然不是像你提出的那样一次拍摄.让我们回顾一下你的理论签名:

impl<T, E> Result<T, E> {
    fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D>
    where
        F: FnOnce(T) -> Result<U, D>,
    {}
}
Run Code Online (Sandbox Code Playgroud)

这不起作用 - 假设我们从一个Err变体开始- 这段代码将如何知道如何转换ED?另外,&self不适合想要转换类型的函数; 那些通常采取self.

您需要组合两个组件:

  1. Result::and_then

    impl<T, E> Result<T, E> {
        fn and_then<U, F>(self, op: F) -> Result<U, E>
        where
            F: FnOnce(T) -> Result<U, E>,
        {}
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. Result::map_err

    impl<T, E> Result<T, E> {
        fn map_err<F, O>(self, op: O) -> Result<T, F>
        where
            O: FnOnce(E) -> F,
        {}
    }
    
    Run Code Online (Sandbox Code Playgroud)

然后,您将需要一个可以表示两种错误类型的类型.我会懒惰和使用Box<Error>

结合在一起,您需要以下内容:

use std::env;
use std::fs::File;
use std::error::Error;

fn main() {
    let conf = env::var("CONF")
        .map_err(|e| Box::new(e) as Box<Error>)
        .and_then(|f| File::open(f).map_err(|e| Box::new(e) as Box<Error>));
}
Run Code Online (Sandbox Code Playgroud)

现在每个调用都将错误值转换为共享类型,结果可以链接and_then.据推测,您的真实代码会创建一个适合您的问题的错误类型,然后您将在map_err调用中使用它.我实施From,那你就可以:

let conf: Result<_, Box<Error>> = env::var("CONF")
    .map_err(Into::into)
    .and_then(|f| File::open(f).map_err(Into::into));
Run Code Online (Sandbox Code Playgroud)


Eri*_*aas 5

如果你真的想忽略你正在做的结果,if let你可以使用这样的宏:

macro_rules! iflet {
    ([$p:pat = $e:expr] $($rest:tt)*) => {
        if let $p = $e {
            iflet!($($rest)*);
        }
    };
    ($b:block) => {
        $b
    };
}


fn main() {
    iflet!([Ok(file) = env::var("CONF")]
           [Ok(mut reader) = File::open(&file)]
           [Ok(conf) = Json::from_reader(&mut reader)] {
        // do something with conf
    });
}
Run Code Online (Sandbox Code Playgroud)

游乐场(不含 Json 部分)

该宏最初来自我对Options上的类似问题所做的回答,但它适用于任何if let. 虽然,Result你经常想以Err某种方式使用 ,所以我通常会倾向于Shepmaster?/解释的方法try!