Blanket conversion from `T: SomeTrait` to `Box<dyn SomeTrait>`

We have all seen how one can use the question mark operator to convert errors into Box<dyn Error>, which especially useful for the main function:

fn main() -> Result<(), Box<dyn Error>> {
    returns_error()?;
    Ok(())
}

This works because there is an implementation of From<T: Error> for Box<dyn Error> (modulo lifetimes). Now I tried to do the same thing but just replacing Error with Debug.

fn main() -> Result<(), Box<dyn Debug>> {
    returns_error()?;
    Ok(())
}

(The idea just being that I don't want to implement Error for all my custom error types, because I don't want to implement Display, but Debug can be easily derived and is enough for my error reporting purposes.)

Surprisingly, this does not work. It looks like there is no corresponding From implementation for Debug, just for Error. This confuses me, because I thought there was a blanket implementation for this for all traits. But it turns out there is just one manual implementation for Error.

So my question is, why is there no blanket implementation of something like

impl<'a, T> From<T> for Box<dyn SomeTrait + 'a>
where
    T: SomeTrait + 'a,

for all traits SomeTrait? (Or maybe auto implementation would be a better term, as it's generic over all traits.)

(In fact I would expect this not just for Box but for all kind of pointers that can hold dyn SomeTrait.)

This is especially annoying since the trait Termination (which is basically what you want to return from the main function) is implemented for all Result<(), E: Debug> (see here), but the above behavior basically means I can't really construct Result<(), E: Debug> in a nice way. (Because I have different error types so I need to work with trait objects as the type E, and then I lose the question mark operator as just explained.) And when I do the work and implement Error for all error types, the implementation won't even get used because it just uses the Debug functionality!

In case you didn't notice, Box<dyn Error> doesn't implement Error because it would cause a coherence problem due to the blanket From implementation. So if it worked in a blanket implementation manner, Box<dyn Trait> could never implement Trait, which would be bad. So that's out.

This similarly kills the workaround of a newtype that implements From<D> where D: Debug from working completely (because the newtype can't implement Debug).

You can combo it with a function wrapper around your real main, though.

Side rant

This -- or something like Anyhow -- is needed for good user-facing errors anyway, because Termination uses Debug and not Display. One of the goals of the Termination trait was to avoid the need for main-wrapping, but IMO it backfired due to this choice.[1]

It does make this easier.

But at this cost.


Because of this,[2] it would have to be compiler magic and not an implementation that one can actually write. Which means it would be some new magical ability that Box and From have. (Granted, Box has some already.)

It would have to also be magical to not conflict with code that has written their own implementations, in order to be backwards compatible -- and to not take away that capability from new Rust code.

Next, it's not always trivially possible, even when the trait is dyn capable.

trait Example {
    fn foo(self) where Self: Sized;
    fn bar(&self);
}

What should the compiler provide for <Box<dyn Example + '_> as Example>::foo? It would have to autogenerate some <SizedImplementor as Example>::boxed_foo(self: Box<Self>) that unboxes for every implementor.

Maybe that doesn't seem unsurmountable, but how about this?

    fn quz(self: Arc<Self>);

You can't unbox and downcast an Arc<Box<dyn Trait>> and create a new Arc<SizedImplementor> to tackle this one.

It would require magic for any such pointer. And...

trait Example {
    fn foo(&mut self);
    fn bar(self);
}

How would the compiler implement this for &dyn Example, Arc<dyn Example>, etc?


I could see there being some sort of macro that works for common cases though.


  1. and I've seen team members agree, but can't be bothered to try to dig it up at the moment ↩︎

  2. in addition to the coherence problem above ↩︎

Eh, follow-up thought, it's worse than that. It just can't provide the implementations without opt-in without effectively taking away the ability to implement it yourself from anyone who hasn't already, because it would be a breaking change to change write an implementation that does something different than whatever the compiler implements.

It would probably also break local negative reasoning and cause other problems I haven't thought of.

Thank you very much for your detailed response! I can see now how it would cause some big problems.

Side rant starts.

But idk, I always feel like Rust puts too little focus on dynamic dispatch. It's lacking so many features (for good reasons or not) that I feel like you can hardly use it emulate something slightly "object oriented". You may be able to use it when your projects starts out, but as your traits and requirements (associated types, serde, supertraits, ...) become for complicated, suddenly you see that you can't use your dyn objects anymore.

The answer is always "doesn't work because of this and that technical reason", and I can (almost) always understand the little technical annoyances which are an artifact of how dynamic dispatch is currently implemented in Rust. But doesn't it all work in C++? I know dynamic dispatch is implemented differently there, but I feel like it could give Rust so much more power if it would just do the same. In fact for all of static dispatch, isn't the only advantage a bit of performance if the compiler can inline some stuff? How much is that performance boost? And it comes at the cost of not even being able to have dynamic linking (=> no easy plugins etc.) and this whole dyn mess. I feel like most Rust users feel a bit of proud when they talk about static linking and feel like dyn is something that's inherently bad (I guess that opinion is just always taken from someone "wiser"), but idk the more I'm programming in Rust the more I feel like one should just have done the "normal" linking from C++. Maybe someone can convince me from the opposite.

EDIT: I guess there is a difficulty because Rust separates data and behavior but I still feel like one can do much better than what dyn currently is. For example if everything is dynamically dispatched then there is no collision for From and one could do the proposed "magic".

Side rant ends.

Well at least I found your cool Rust advice book with a chapter on dynamic linking, I will read that and maybe it will at least help me to navigate the current Rust implementation.