我应该放弃使用 `and_then` 并始终使用 `?` 运算符吗?

Should I ditch using `and_then` and always use the `?` operator?

我想写这样的东西,但由于类型不匹配,它无法编译:

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = std::env::args()
        .nth(1)
        .ok_or("1 arg is expected")
        .and_then(std::fs::File::open)
        .and_then(serde_yaml::from_reader)?;
}

因为在每个闭包中添加一个 map_err 看起来很慢而且 'boiler platy',我用类似的东西替换它:

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = serde_yaml::from_reader(std::fs::File::open(
        std::env::args().nth(1).ok_or("1 arg is expected")?,
    )?)?;
}

第一个感觉更自然,读起来像英文,而第二个感觉有点倒退。

我是否应该放弃 and_then 并始终使用 ? 运算符?

如果不是,有没有办法使结果组合器像 ? 运算符一样平滑?

Should I ditch the and_then and always use the ? operator ?

这是个人判断,只有你能回答。

Is there a way to make result combinator as smooth as the ? operator ?

坦白说没有。 ? « 隐式 » 执行转换(不是 真正地 隐式,因为它是其工作的重要组成部分,但不必单独调用转换,也许是 « 简洁地 »? ), and_then 没有。这意味着在使用 and_then 时,您必须自己执行这些转换。这似乎合乎逻辑。

您也许可以为此创建一个方便的宏。或者添加一个扩展方法或包装器类型,可以在幕后执行这些转换。

您的第一个表达式不起作用的原因是错误类型不匹配。更具体地说,

let val = std::env::args()
        .nth(1)
        .ok_or("1 arg is expected")
        .and_then(std::fs::File::open)
        .and_then(serde_yaml::from_reader)?;

std::env::args().nth(1)return一个Option<T>。如果您查看 ok_or 签名,即 pub fn ok_or<E>(self, err: E) -> Result<T, E>。这意味着对于您的情况,ok_or("1 arg is expected") 您的 return 类型是 Result<T, &str>。所以你这里的错误类型是 &str 因为在 ok_or 中你传递了一个字符串切片作为错误类型。

如果你看一下 and_then 方法,签名是 pub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>,基本上这意味着你传入的 Function and_then 应该与原来的错误类型相同结果,是 &str。这与行 .and_then(serde_yaml::from_reader) 相同,所有“链接”and_then 函数的错误类型需要一致。

如果你真的想要,你可以通过以下方式让你的代码通过编译。或者你可以创建一个统一的错误,这样就没有不匹配的错误类型。然后就可以用ok_or(...).and_then(...).and_then(...)那种写法了。您只需要匹配错误类型即可。

工作示例

fn custom_fs_open(path: String) -> Result<String, &'static str>{
    // actual logics to open fs
    Ok(String::from("abc"))
}


fn custom_serde_yaml(buf: String) -> Result<String, &'static str>{
    // actual logics to do serde convertion
    Ok(String::from("cde"))
}


fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let val = std::env::args()
            .nth(1)
            .ok_or("1 arg is expected")
            .and_then(custom_fs_open)
            .and_then(custom_serde_yaml)?;
            
    Ok(())
}