Wrapping boxed errors

Hi all!

I'm trying to write a simple library function that takes a closure as argument, does its own stuff, repacks errors from the closure and retunrs them.

I've been banging my head into problems with this function for more than a week now, and now I'm stuck again.

In the user code I want to have as simple error handling as possible, so I'm using type AnyError = Box<dyn std::error::Error + 'static>;. So for my library function I've created an error type that wraps anything implementing error and was hoping it would just contain the boxed errors and behave well.

Unfortunately the compiler complaints about not knowing size at compile time ... I don't understand why, because I was expecting to be just saving Boxes of the unsized types ...

Can you please help me understand this puzzle?

Here's a minimalized version of the problem:

////////////////////////////// lib.rs
#[derive(Debug)]
enum Err <E1>
where
    E1: std::error::Error + 'static,
{
    X {source: E1},
}

impl <E1> std::fmt::Display for Err<E1>
where
    E1: std::error::Error + 'static,
{
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Err::X") }
}

impl <E1> std::error::Error for Err<E1>
where
    E1: std::error::Error + 'static, {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::X{source} => Some(source)
        }
    }
}

fn library_function<F1, E1>(f1: F1) -> Result<(), Err<E1>>
where
    F1: FnOnce() -> Result<(), E1>,
    E1: std::error::Error + 'static,
{
    f1().map_err(|source| Err::X { source })?;
    Ok(())
}

////////////////////////////// user.rs

type AnyError = Box<dyn std::error::Error + 'static>;
type SimpleResult = Result<(), AnyError>;

fn main() -> SimpleResult {
    library_function(|| -> SimpleResult { Ok(()) })
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
  --> src/main.rs:42:5
   |
27 | fn library_function<F1, E1>(f1: F1) -> Result<(), Err<E1>>
   |    ----------------
...
30 |     E1: std::error::Error + 'static,
   |         ----------------- required by this bound in `library_function`
...
42 |     library_function(|| -> SimpleResult { Ok(()) })
   |     ^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::error::Error + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<(dyn std::error::Error + 'static)>`

error[E0308]: mismatched types
  --> src/main.rs:42:5
   |
41 | fn main() -> SimpleResult {
   |              ------------ expected `std::result::Result<(), std::boxed::Box<(dyn std::error::Error + 'static)>>` because of return type
42 |     library_function(|| -> SimpleResult { Ok(()) })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::boxed::Box`, found enum `Err`
   |
   = note: expected enum `std::result::Result<_, std::boxed::Box<_>>`
              found enum `std::result::Result<_, Err<std::boxed::Box<_>>>`

error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
  --> src/main.rs:42:5
   |
3  | / enum Err <E1>
4  | | where
5  | |     E1: std::error::Error + 'static,
6  | | {
7  | |     X {source: E1},
8  | | }
   | |_- required by `Err`
...
42 |       library_function(|| -> SimpleResult { Ok(()) })
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `(dyn std::error::Error + 'static)`
   = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
   = note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<(dyn std::error::Error + 'static)>`

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Ok, i'm puzzled. This is the minimum reproducable example I can find: Playground

Looks like the compiler doesn't make sense...

use std::error::Error;

fn library_function<F1, E1>(f1: F1) -> Result<(), E1>
where
    F1: FnOnce() -> Result<(), E1>,
    E1: Error, // <-remove this line -> compile
{
    f1()
}

fn main() -> Result<(), Box<dyn Error>> 
{
    library_function( || Ok(()) )
}
1 Like

Actually the issue lies in the error message being absurd, I guess this could be filed as a bug for the compiler diagnostics / error reporting:

E1 and AnyError are not the same same type, so you'd need something like ? unwrapping or .map_err(Into::into) to make the conversion work. At which point the error becomes clearer: Rust doesn't know which error type to pick for the unfallible function given in main, leading to a type inference error.

Actually, that's not it. The issue is that Box<dyn Error> does not implement Error!, which is either an oversight or was done on purpose to avoid coherence issues with From impls for Box.

Even with the blanket impl?

impl<T: Error> Error for Box<T>

The blanket impl needs a ?Sized "unbound" / bound remover to allow for it to include the T = dyn Error case (all generic types have an implicit Sized bound). That's why I say it could have been an oversight, but I think it would have been fixed by now. So I'm pretty sure it has to due with avoid From conflicts to enable E -> Box<dyn Error> automatic conversion with the ? operator.

1 Like

oh wow, now the error message actually makes some sense as to why it's talking about Sized.

I will file an issue, because that's really cryptic. Feels like the compiler wants to be to smart here. Just saying Error isn't implemented for Box<dyn Error> would be more straight forward.

I added it as an example to: Note where implicit Sized requirement comes from

1 Like

Screenshot 2020-03-02 at 18.54.13

The following two impls conflict, when Box<dyn Error> : Error

// `E = Box<dyn Error + '_>`
impl<E : Error> From<E> for Box<dyn Error + '_> {
// T = Box<dyn Error +'_>
impl<T> From<T> for T {
2 Likes

Thanks for all the answers. I've solved this by makign a custom trait ErrorSource that is implemented for Error and for dyn Error separately.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.