在Rust中跨多个线程使用`Result<T, Box<dyn Error>>`技巧

8

在IT技术中,一种常见的技巧是使用返回Result<X, Box<dyn Error>>来注释函数,以便允许它们返回任何错误。然而,如果没有实现Send,则无法从线程返回此类型的错误本身。例如,下面的代码:

use rayon::prelude::*; // 1.5.1
use std::error::Error;

fn main(){
    ["1", "2", "three"]
        .into_par_iter()
        .try_for_each(|i| -> Result<usize, Box<dyn Error>> {
            let inner = i.parse::<usize>()?;
            Ok(inner)
        }
    );
}

给出以下错误:
error[E0277]: `dyn std::error::Error` cannot be sent between threads safely

(编程示例)


另一方面,如果您尝试指定错误必须实现Send,那么?操作符将不再起作用:

use rayon::prelude::*; // 1.5.1
use std::error::Error;

fn main(){
    ["1", "2", "three"]
        .into_par_iter()
        .try_for_each(|i| -> Result<usize, Box<dyn Error + Send>> {
            let inner = i.parse::<usize>()?;
            Ok(inner)
        }
    );
}

error[E0277]: `?` couldn't convert the error to `Box<dyn std::error::Error + Send>`

我如何继续使用 Box<dyn Error> 的快捷方式,但仅针对可以发送的错误,使其跨线程工作?(playground)


2
根据我的经验,这种“常见技巧”总会以某种方式反噬你,相比之下,最好定义自己的错误类型。thiserror 装箱是我个人最喜欢的方法,可以减少样板代码,但还有其他几种替代方案。 - Thomas
我同意Thomas的观点。你真正需要一个函数能够以不加区分的方式返回每种类型的错误有多频繁,而它只是一个替代品,让你不必考虑可能出现什么问题?你所做的就是向Rust添加类似于C++风格的未经检查的异常,并使任何人都无法做除记录错误和恐慌之外的任何事情。 - Silvio Mayolo
你可能是对的,我可以在这里使用anyhow创建。但这是处理动态错误的“内置”方式。它解决的问题是有一个可能引发多种不同错误类型的闭包,而我不能打扰使用自己的错误类型来统一它们。 - Migwell
1
@SilvioMayolo 有时候你只是想向用户报告错误-例如,当你接受一个可以以你无法预料的方式失败的回调时。这种需求由 anyhow crate 满足,其 titular 类型基本上是对 Box<dyn Error + Send + Sync + 'static> 的包装,其流行程度表明存在充分的需求。(它的作者 David Tolnay 还写了 thiserrorserde 等等。)与 C++ 异常不同,传播是显式的,涉及返回 Result,通常使用 ? 运算符,因此没有什么事情会在背后发生。 - user4815162342
1个回答

10
你要找的是std的实现,可以参考这里
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>

把你的闭包的返回类型改为Result<usize, Box<dyn Error + Send + Sync>>,解决不相关的类型错误,然后它就可以编译了。


很棒的解决方案,特别是因为它使用了 std 中已经存在的 trait 实现,并且文档详细解释了它。为了其他人未来的参考,这里有一个使用我的原始示例的 playground:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6c745d56e7b055a6eb39f693ddc910f1 - Migwell
如果这是您正在寻找的解决方案,请将其标记为已接受,以便关闭问题。 - user2722968
尽管看起来所有你自己的 Result<X, Box<dyn Error>> 函数,在 Result<X, Box<dyn Error + Send + Sync>> 函数中使用 ? 的调用和使用也必须修改为相同的签名。 - Migwell
2
下次请给我留出稍微超过14分钟的时间来探讨解决方案的优缺点,谢谢。 - Migwell
你可以使用 try_for_each(...).map_err(|e| e as Box<dyn Error>) 来弱化类型,这是可能的,因为你不再在 try_for_each 之外使用线程。错误类型将变为 Box<dyn Error>,应该与其他所有内容兼容。 - user2722968
考虑添加'+ 'static',以便在(非作用域)线程之间传输错误,并将其转换为'Anyhow'。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接