I’ve occasionally run into cases where I want the implementation of a trait (although it could also be just a function) to depend on whether a type parameter implements a trait or not.
As a concrete example, suppose I have an Error type like this:
enum Error<E> {
Tls(native_tls::Error),
Other(E),
}
I’d like to be able to implement std::error::Error
for it regardless of the type of E, but if E implements std::error::Error
itself, then provide a better implmentation. Something like:
impl<E> fmt::Display for Error<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Tls(err) => err.fmt(fmt),
Error::Other(err) => if (implements!(E, fmt::Display) { err.fmt(fmt) } else { fm.write_str("other error") }
}
}
}
impl<E> error::Error for Error<E> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Tls(err) => Some(err),
Error::Other(err) => if (implements!(E, fmt::Display)) { Some(err) } else { None }
}
}
}
Of course the implements!
macro is just example syntax. Such a macro couldn’t actually be implemented with the current macro system.
With the current langague afaik something like this isn’t possible. Instead I have to define implementations with where
constraints like E: Display
and E: error::Error + 'static
. But then if those constraints aren’t met, then Error
doesn’t implement Display
or std::error::Error
.
I can think of a few ways this could be handles:
- Negated type bounds: Allow defining both
impl<E> ... where E: Display
andimpl<E> .. where E: !Display
. This is a workable solution, but for cases like my example it isn’t very ergonomic or elegant. - Specialization: I think (although I’m not 100% sure) that the accepted specialization api would provide a way to do this (using default implementations). But like negated type bounds, it isn’t terribly ergonomic since the entire implementation would have to be written out for both conditions.
- Have a built in macro or syntax for such conditions. I don’t think this is likely to happen.
- Allow defining macros that can use type information. If you could write a macro (either procedural or declaritive) that had information about the actual type of
E
during monomorphization, then it would be possible to define a macro likeif_implements!(E: Display { ... } else { ..})
. This is probably the most general solution, since it would allow other compile-time conditions as well, and allow more expermentation in macros without having to make changes to the language. However, I have no idea how feasible it is to support macros with type information.