My understanding of the orphan rule of interest is that:
- For any
impl
of a Trait on a Type, either the Trait or the Type must be defined in the same crate as theimpl
.
or equivalently:
- It is impossible to implement a trait defined in a foreign crate on a type which is also defined in a foreign crate.
So the question is, how should one design a library crate that provides a type which should implement a foreign trait, but leave it to the consumer crate to define the implementation of that trait?
For example, imagine a hypothetical library crate, cards
, which provides types representing cards in a standard 52-card deck of playing cards intended for use in external crates to facilitate the development of card game programs. Such a library might provide the following type:
/// Ranks of a standard French-suited playing card
pub enum Rank {
Ace,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
}
Since a substantial portion of popular card games involve comparing the ranks of 2 cards, it should make sense for enum Rank
to implement std::cmp::PartialOrd
so that ranks can easily be compared with the <
, >
, <=
, and >=
operators. But crate cards
should not define this implementation because the ranks of playing cards are not intrinsically equipped with a partial ordering relation; such a relation is instead optionally imposed on them by the specific game within which the cards are being used and in general, the implementation varies by game. For example, some games consider Ace
to be greater than all other ranks while others consider Ace
to be less than all other ranks and certain games may not even compare ranks at all and therefor not need a PartialOrd
implementation.
In keeping with best practice regarding separation of concerns, it seems obvious that crate cards
should not implement PartialOrd
, but instead leave its implementation up to the consumers of cards
, allowing each consumer to define their own partial ordering relation for the ranks. However, since in a crate depending on cards
, both Rank
and PartialOrd
are crate-foreign, this is prohibited by the Orphan rule.
What is the best way to deal with this scenario? It seems to me the only correct thing to do is to refrain from implementing PartialOrd for Rank
and let the consumers make their own functions for rank comparison like:
fn blackjack_rank_compare(first: &Rank, second: &Rank) -> Option<Ordering> {...}
fn poker_rank_compare(first: &Rank, second: &Rank) -> Option<Ordering> {...}
fn war_rank_compare(first: &Rank, second: &Rank) -> Option<Ordering> {...}
// etc.
However this is inconvenient. Is there any viable workaround for this problem?