是否有更好的功能方法来处理带错误检查的向量?

Bob*_*Bob 7 functional-programming rust

我正在学习Rust,并想知道如何改进下面的代码.

我有一个形式元组的向量(u32, String).该u32值代表行号和Strings为在相应的行中的文本.只要所有String值都可以成功解析为整数,我想返回一个Ok<Vec<i32>>包含刚刚解析的String值,但如果不是,我想返回某种形式的错误(只是Err<String>在下面的例子中).

我正在努力学习避免可变性并在适当的地方使用功能样式,如果只是需要的话,上面的功能很简单.这是我在这种情况下想出的:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    sv.iter()
        .map(|s| s.1.parse::<i32>()
                    .map_err(|_e| "*** Invalid data.".to_string()))
        .collect()
}
Run Code Online (Sandbox Code Playgroud)

但是,小问题是我想为每个无效值(而不仅仅是第一个)打印错误消息,并且错误消息应包含违规元组中的行号和字符串值.

我已设法使用以下代码执行此操作:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    sv.iter()
        .map(|s| (s.0, s.1.parse::<i32>()
                  .or_else(|e| {
                      eprintln!("ERROR: Invalid data value at line {}:  '{}'",
                                s.0, s.1);
                      Err(e)
                  })))
        .collect::<Vec<(u32, Result<i32, _>)>>() // Collect here to avoid short-circuit
        .iter()
        .map(|i| i.1
             .clone()
             .map_err(|_e| "*** Invalid data.".to_string()))
        .collect()
}
Run Code Online (Sandbox Code Playgroud)

这有效,但看起来相当混乱和繁琐 - 特别是collect()在中间键入以避免短路,因此所有错误都被打印出来.这个clone()调用也很烦人,而且我不确定为什么需要它 - 编译器说我正在逐渐退出借来的内容,但我不确定是什么被移动了.有没有办法可以更干净地完成?或者我应该回到更程序化的风格?当我尝试时,我最终得到了可变变量和一个表示成功和失败的标志,这看起来不那么优雅:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
    let mut datavals = Vec::new();
    let mut success = true;
    for s in sv {
        match s.1.parse::<i32>() {
            Ok(v) => datavals.push(v),
            Err(_e) => {
                eprintln!("ERROR: Invalid data value at line {}:  '{}'",
                          s.0, s.1);
                success = false;
            },
        }
    }
    if success {
        return Ok(datavals);
    } else {
        return Err("*** Invalid data.".to_string());
    }
}
Run Code Online (Sandbox Code Playgroud)

有人可以告诉我最好的方法吗?我应该坚持这里的程序风格,如果可以的话,可以改进吗?或者有更清洁的功能性方法吗?还是两者的混合?任何建议表示赞赏

Sir*_*ius 2

在纯粹的函数式风格中,您必须避免副作用。打印错误是一种副作用。首选样式是返回以下样式的对象:

Result<Vec<i32>, Vec<String>>
Run Code Online (Sandbox Code Playgroud)

并在函数返回后打印列表data_vals。

因此,本质上,您希望处理收集整数列表和字符串列表:

fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, Vec<String>> {
    let (ok, err): (Vec<_>, Vec<_>) = sv
        .iter()
        .map(|(i, s)| {
            s.parse()
                .map_err(|_e| format!("ERROR: Invalid data value at line {}: '{}'", i, s))
        })
        .partition(|e| e.is_ok());

    if err.len() > 0 {
        Err(err.iter().filter_map(|e| e.clone().err()).collect())
    } else {
        Ok(ok.iter().filter_map(|e| e.clone().ok()).collect())
    }
}

fn main() {
    let input = vec![(1, "0".to_string())];
    let r = data_vals(&input);
    assert_eq!(r, Ok(vec![0]));

    let input = vec![(1, "zzz".to_string())];
    let r = data_vals(&input);
    assert_eq!(r, Err(vec!["ERROR: Invalid data value at line 1: 'zzz'".to_string()]));
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

这使用partition不依赖于外部板条箱。