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.