【问题标题】:Is there a way to chain Result mapping and unwrapping?有没有办法链接结果映射和展开?
【发布时间】:2016-12-13 19:12:05
【问题描述】:

我有这个代码。

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
        }
    }
}

我正在努力让它不那么像一棵节日圣诞树,并且正在考虑用链子连接。请注意,这条链中的每一步都会产生另一个Result,所以显然这不起作用(我们在Result 中得到Result)。

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

// do something with conf

我的错误类型也因每个步骤而异,这意味着我不能只将.map 替换为.and_then

我想我正在寻找类似于 JavaScript 的承诺的东西。也就是说,从 Promise 内部返回的 Promise 会解开内部 Promise。签名大概应该是这样的:

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>
}

Rust 中有这样的机制吗?还有其他方法可以摆脱我的节日圣诞树吗?

【问题讨论】:

  • 其实不久前有一篇很棒的文章:mgattozzi.com/posts/russian-dolls.html
  • 谢谢!我去看看!
  • 我用try!()
  • try!()(和?-operator)在Err 上都有早期回报,我不希望这样。
  • 我正在尝试重现,Json 指的是什么? serialize::json?还是serde?​​span>

标签: rust


【解决方案1】:

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>,
    {}
}

这是行不通的——假设我们以Err 变体开始——这段代码怎么知道如何从E 转换为D?此外,&amp;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>,
        {}
    }
    
  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,
        {}
    }
    

那么您将需要一个可以表示两种错误类型的类型。我会偷懒用Box&lt;Error&gt;

结合在一起,你需要这样的东西:

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>));
}

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

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

【讨论】:

  • Into::into 是个不错的选择。有没有像这样的“免费”功能来获得参考? String -> &amp;String?
  • @MartinAlgesten 我认为您正在寻找AsRef::as_ref,但在您想要的情况下它可能不起作用。例如,您不能将Option&lt;String&gt; 映射到Option&lt;&amp;str&gt;,因为它会引用随后将被删除的内容。不过有Option::as_ref
【解决方案2】:

如果您真的想像使用 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
    });
}

Playground (without Json part)

宏最初来自an answer I made to a similar question on Options,但它适用于任何if let。不过,对于Result,您通常希望以某种方式使用Err,所以我通常倾向于the approach explained by Shepmaster?/try!

【讨论】:

  • 宏当然是一种方式。谢谢!
【解决方案3】:

anyhow crate 可以将不同的错误类型链接在一起,结合? 运算符,您可以非常简洁地表达Results 的序列。缺点是你需要从函数中返回一个Result,所以在某些情况下你需要创建一个助手。如果你只需要一个地方的帮助器,虽然你可以在调用函数中定义它,这样它就不会污染周围的命名空间。

这是一个简单的example,我想从环境变量中获取usize

fn main() {
    fn env_int(key: &str) -> anyhow::Result<usize> {
        Ok(std::env::var(key)?.parse()?)
    }

    println!("{:?}", env_int("MISSING"));
    println!("{:?}", env_int("CARGO_PKG_VERSION"));
    println!("{:?}", env_int("PLAYGROUND_EDITION"));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-28
    • 2018-11-14
    • 2020-12-16
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    相关资源
    最近更新 更多