Conditional implementation based on whether a type parameter satisfies a bound


#1

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 and impl<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 like if_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.

#2

Specialization should be able to cover this.


#3

… and AFAIK macro processing occurs during AST formation, before type information is available.


#4

Yes, this is handled through specialization with two impls: one for Error<E> and one for Error<E: Display>.


#5

macro processing occurs during AST formation, before type information is available

yes, I’m aware of that. in order to implement the if_implements macro, a new type of macro that is evaluated after type information is available. Having the ability to write such macros would allow other things like automatic delegation and compile-time dependency injection that can’t be done currently.


#6

So does this new type of macro, which presumably executes a deferred segment of previously-generated AST, run in HIR or in MIR? Does it trigger recursion from MIR to HIR, or do you need it to generate more AST and recur to a yet-earlier point in the compiler? What is the overall impact of this augmented compiler flow path on the compiler data structures?

Addendum: Might it be possible to experiment with this piecemeal via compiler plugins?


#7

The macro thing is not relevant here. The OP says it’s just an example syntax. Please don’t drop a pile of compiler jargon on the poor thing for no reason.

What the OP is asking for is generic specialization, which is blocked on a formal proof of parts of the type system.