Using thiserror with Box<dyn>

Hi there forum users,

I'm starting to use the thiserror crate and would like to understand the transparent option. It works for a simple use-case, but for example the following one fails. I tried to understand what's going on, but, honestly, I only see that the Boxed enum from Msg makes it impossible for the thiserror::Error derive macro to function properly. But I have no idea if it's actually possible to make it work easily.

use thiserror::Error;
use std::sync::mpsc::channel;

trait Msg2{
    fn msg(&self) -> String;
}

enum Msg {
    Something,
    Boxed(Box<dyn Msg2>),
}

#[derive(Error, Debug)]
enum TestError {
    #[error(transparent)]
    Queue(#[from] std::sync::mpsc::SendError<Msg>)
}

fn send_msg() -> Result<(), TestError>{
    let (tx, _rx) = channel::<Msg>();
    tx.send(Msg::Something)?;
    Ok(())
}

fn main(){
    match send_msg(){
        Err(e) => print!("{:?}", e),
        Ok(()) => print!("success"),
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0599]: the method `as_dyn_error` exists for reference `&SendError<Msg>`, but its trait bounds were not satisfied
   --> src/main.rs:13:10
    |
13  | #[derive(Error, Debug)]
    |          ^^^^^ method cannot be called on `&SendError<Msg>` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `SendError<Msg>: std::error::Error`
            which is required by `SendError<Msg>: AsDynError`
            `&SendError<Msg>: std::error::Error`
            which is required by `&SendError<Msg>: AsDynError`
    = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground`

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

The problem here is that SendError doesn't implement Error for types which aren't Send, and Msg isn't Send because not everything that implements Msg2 may be Send. The solution is to change the Boxed enum variant to Boxed(Box<dyn Msg2 + Send>) (playground), since whatever you are sending on the channel should be Send anyway.

Thanks, it all makes sense now. Well, not all, but some more :slight_smile:

You should also consider not putting SendError in the error type at all. SendError has the special purpose of returning the message to the caller (so they can retrieve it on failure rather than it being dropped), but the message value itself doesn't necessarily make sense to propagate up. Since SendError doesn't carry any other useful information at all, consider just having a "failed to send message" variant that doesn't wrap the SendError.

That's all assuming that TestError is being returned from some function that uses a channel as an implementation detail. If it is explicitly part of the purpose, and the messages are caller-provided, then it makes sense to return the message.

1 Like

I love Rust. I started programming 40 years ago, and using Rust it feels like I'm just starting now!

What you write makes sense, and I'll have to change that in the actual program I'm writing. Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.