Trait conciliator::Pushable

source ·
pub trait Pushable<M>: Sealed<M> {
    // Required method
    fn push_into(&self, buffer: &mut Buffer);
}
Expand description

Types that are Inline or Display

This is a helper trait that is blanket-implemented for both Inline and Display types. Display types are written to the Buffer without colors. Useful for functions/structs where the marker type M can be inferred.

This trait is sealed and cannot be implemented.

§Implementation

Because Rust does not have any specialization or way to express where T: NOT Trait, it is not possible to blanket-implement the (exact) same trait twice in such a way that a conflict could exist. It would be impossible to blanket-implement a trait Pushable { … } for both Inline and Display types: a type could exist that is itself Inline as well as Display and this type would have two different implementations of Pushable.

The workaround used to resolve this is described in detail below.

This trait has a generic type parameter M that exists solely as a “marker” to disambiguate implementations. Then a different empty marker type is specified for each of the implementations:

  • impl<T: Display> Pushable<marker::AsDisplay> for T
  • impl<T: Inline> Pushable<marker::AsInline> for T

This sidesteps the conflict: Pushable<AsDisplay> and Pushable<AsInline> are essentially just two different traits - there is no issue with having both implemented for the same type. But then again, two different traits is what we started with: Inline and Display. So, why bother?

The trick to using this trait is to be generic over a T: Pushable<M> for any M and getting type inference to figure out the correct M. So, for a function to accept any type that is either Inline or Display, it would look like this:

fn push_any<M, T: Pushable<M>>(thing: T) {}

Somewhat surprisingly, this works very well!
The marker type is (correctly) inferred for all types that are either Inline or Display. Theoretically, it is even possible to pick which implementations to use when both apply by disambiguating the function call with type annotations like so: push_any::<marker::AsInline, _>(…) (note the _ to have the rest inferred). But because this is inconvenient in practice, the marker types are (currently) not exposed.

§Caveats

Arguably, this workaround is only really useful in cases where type inference can be relied on (certainly, those are the only cases where it is convenient). Unfortunately, type inference can only be relied on as much as the types can be relied on to not implement the other trait. In practice, this means that one should think about whether a type will need to implement both Inline and Display beforehand (and consider whether using a newtype instead is better). Otherwise, it is possible to wind up having to fix all the places where type inference was relied on but no longer can be.

Crucially, for a type that is Inline or Display, implementing the other trait as well could be a breaking change. This is surprising because generally implementing a trait is not considered breaking, but in this case it could lead to code failing to compile with type annotations needed.
Because of Rust’s orphan rules, this problem is merely theoretical when it comes to third party crates. For any type, such a surprise breaking change could only be introduced by conciliator implementing Inline for a third-party type that is already Display (I won’t do this) or a third-party crate deciding to depend on conciliator and doing so for it’s own type (very unlikely).
Note though that this only applies because crates are unable to implement Pushable directly.

Required Methods§

source

fn push_into(&self, buffer: &mut Buffer)

Either write to a buffer without colors using Display, or Inline into it

Implementors§