8

It's a common trick to annotate your functions with returning Result<X, Box<dyn Error>> to allow them to return any error at all. However, you can't return this from a thread without the error itself implementing Send. For example this code:

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

Gives this error:

error[E0277]: `dyn std::error::Error` cannot be sent between threads safely

(playground)


On the other hand, if you try to specify that the error must implement Send, the ? operator no longer works:

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

(playground)

How can I continue to use the Box<dyn Error> shortcut, but only for errors that can be sent, allowing it to work across threads?

Migwell
  • 18,631
  • 21
  • 91
  • 160
  • 2
    In my experience, this "common trick" always comes back to bite you in such ways, and you're better off defining your own error type. The `thiserror` crate is my personal favourite to do this with a tolerable amount of boilerplate, but there are several alternatives. – Thomas Aug 22 '21 at 15:25
  • I agree with Thomas. How often do you actually *need* a function to be able to return every type of error in an indiscriminate way, and how often is it just a crutch so you don't have to think about what sorts of things can go wrong? All you're doing is adding C++-style unchecked exceptions to Rust and making it completely impossible for anyone to do anything other than log the errors and panic. – Silvio Mayolo Aug 22 '21 at 15:29
  • You're probably right, and I could be using the `anyhow` create here. But this is the "built-in" way to handle dynamic errors. The problem it's solving is having a closure that could raise multiple different error types, when I can't be bothered unifying them using my own error types. – Migwell Aug 22 '21 at 15:46
  • 1
    @SilvioMayolo There _are_ situations when you just want to report failure to the user - e.g. when you accept a callback that can fail in ways you cannot anticipate. That need is covered by the `anyhow` crate, whose titular type is basically a wrapper around `Box`, and whose popularity shows that there is ample need for this. (Its author is David Tolnay who also wrote `thiserror` and `serde`, among others.) Unlike with C++ exceptions, propagation is explicit, involves returning `Result`, and typically uses the `?` operator, so nothing happens behind your back. – user4815162342 Aug 22 '21 at 17:31

1 Answers1

10

The implemention in std you are looking for is this:

impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>

Change your closure's return type to Result<usize, Box<dyn Error + Send + Sync>>, fix the unrelated type error, and it will compile.

user2722968
  • 13,636
  • 2
  • 46
  • 67
  • Great solution, particularly because it uses a trait implementation already in `std`, and the docs explain it in detail. For others' future reference, here's a playground using this solution with my original example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6c745d56e7b055a6eb39f693ddc910f1 – Migwell Aug 22 '21 at 15:43
  • If this is the solution you are looking for, please mark it as accepted so the question is closed. – user2722968 Aug 22 '21 at 15:57
  • Although it seems that all your own `Result>` functions that you call and use `?` on within the `Result>` function must also be modified to have the same signature. – Migwell Aug 22 '21 at 15:58
  • 2
    Please leave me slightly more than 14 minutes to explore the pros and cons of the solution next time, thanks. – Migwell Aug 22 '21 at 16:00
  • You can `try_for_each(...).map_err(|e| e as Box)` to weaken the type, which is possible because you are no longer using threads outside of `try_for_each`. The error type will then be `Box`, which should be compatible with everything else. – user2722968 Aug 22 '21 at 16:02
  • Consider also adding `+ 'static` to be able to transport the error between (non-scoped) threads, and to be able to convert it to `Anyhow`. – user4815162342 Aug 22 '21 at 17:55