我可以简化处理Option <t>和Result <t>的Rust代码吗?

时间:2017-12-05 09:13:53

标签: error-handling rust optional

我正在研究我的第一个实际的Rust程序,一个自定义的i3status。它很顺利,但有两个地方我必须处理Result<T>Option<T>并且代码看起来很丑,让我相信我错过了一些语言功能或库函数来写这些部分更干净。

decaffeinate,但我会在这里引用相关部分。第一个是:

fn read_number_from_file(path: &str) -> Option<i64> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(_) => return None,
    };
    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => {},
        Err(_) => return None,
    };
    match contents.trim().parse::<i64>() {
        Ok(val) => Some(val),
        Err(_) => None,
    }
}

此处吞咽错误信息是故意的。 i3status没有stderr,所以我所能做的就是跳过渲染损坏的部分。

无论如何,此代码看起来很丑陋,重复使用match来丢弃Err值。我尝试使用新的?运算符,使返回类型为std::io::Result<i64>,但str::parse返回不同类型的错误,因此AFAICS不起作用。因此,我将Option<i64>作为最低共同标准。

第二个丑陋的部分是使用此功能的地方:

let energy_full = match read_number_from_file(ENERGY_FULL_PATH) {
    Some(val) => val,
    None      => return Vec::new(),
};
let energy_now = match read_number_from_file(ENERGY_NOW_PATH) {
    Some(val) => val,
    None      => return Vec::new(),
};
let is_charging = match read_number_from_file(POWER_ONLINE_PATH) {
    Some(val) => val > 0,
    None      => return Vec::new(),
};

我觉得应该能够将每个match表达式合并到.or_else()这样的函数调用中,但是.or_else(|| return Vec::new())显然不起作用return的范围是lambda而不是原始函数。

所以,在这两种情况下,我的问题是我是否可以用更紧凑和惯用的东西来取代match

2 个答案:

答案 0 :(得分:7)

要将Option<T>Result<T, E>合并,请使用ok()和问号运算符:

fn read_number_from_file(path: &str) -> Option<i64> {
    let mut file = File::open(path).ok()?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).ok()?;
    contents.trim().parse::<i64>().ok()
}

至于问题的第二部分,如果你可以在一个函数中包含所有这些变量绑定:

fn foo() -> Option<Vec<i64>> { // or some other Option<Vec>
    let energy_full = read_number_from_file(ENERGY_FULL_PATH)?;
    let energy_now = read_number_from_file(ENERGY_NOW_PATH)?;
    let is_charging = read_number_from_file(POWER_ONLINE_PATH)? > 0;

    Some(Vec::new()) // placeholder for the valid return value
}

然后,您可以对其结果使用unwrap_or_else(),以便在出现任何错误时返回空Vec

let foo = foo().unwrap_or_else(|| vec![]);

或仅unwrap_or(),因为empty vectors don't allocate memory

let foo = foo().unwrap_or(vec![]);

答案 1 :(得分:2)

对于第二部分,您可以使用crates.io上提供的其中一个理解包,例如map_forcompmdo。例如map_for

return map_for!(
    energy_full  <- read_number_from_file(ENERGY_FULL_PATH);
    energy_now   <- read_number_from_file(ENERGY_NOW_PATH);
    power_online <- read_number_from_file(POWER_ONLINE_PATH);
    is_charging  = power_online > 0;
    => vec![ energy_full, energy_now ]
).unwrap_or_else (|| vec![]);

完全披露:我是map_for crate的作者。