My understanding of the orphan rule of interest is that:
impl
of a Trait on a Type, either the Trait or the Type must be defined in the same crate as the impl
.or equivalently:
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 implementstd::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?
There are a lot of answers to your question.
You could for example use a type parameter for defining the ordering ( playground ):
use core::cmp::{ PartialOrd, Ordering, };
use std::marker::PhantomData;
pub enum Card {
Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King,
}
pub use Card::{
Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King,
};
// Evaluated card structure; default game is DefaultT
pub struct MyValuedCard<T=DefaultT>(Card,PhantomData<T>);
impl<T> MyValuedCard<T> {
pub fn new(card: Card) -> Self { Self(card,PhantomData) }
}
trait MyOrder { // Trait for customizing the order
fn order(c: &Card) -> u8;
}
// PartialEq and PartialOrd implementation
impl<T> PartialEq for MyValuedCard<T> where T: MyOrder {
fn eq(self: &Self, &Self(ref second, _): &Self,) -> bool {
PartialEq::eq(&T::order(&self.0), &T::order(second))
}
}
impl<T> PartialOrd for MyValuedCard<T> where T: MyOrder {
fn partial_cmp(self: &Self, &Self(ref second, _): &Self,) -> Option<Ordering> {
PartialOrd::partial_cmp(&T::order(&self.0), &T::order(second))
}
}
// Default game with its order
pub enum DefaultT {}
impl MyOrder for DefaultT {
fn order(c: &Card) -> u8 {
match c {
Ace => 1, Two => 2, Three => 3, Four => 4, Five => 5, _ => 6,
}
}
}
// Game G1 defined by user
pub enum G1 {}
impl MyOrder for G1 {
fn order(c: &Card) -> u8 {
match c {
Ace => 1, Two => 2, Three => 3, Four => 4, Five => 5, _ => 6,
}
}
}
// Game G2 defined by user
pub enum G2 {}
impl MyOrder for G2 {
fn order(c: &Card) -> u8 {
match c {
Ace => 6, Two => 5, Three => 4, Four => 3, Five => 2, _ => 1,
}
}
}
fn main() {
// Default game
let ace: MyValuedCard = MyValuedCard::new(Ace);
let king: MyValuedCard = MyValuedCard::new(King);
// Game G1
let ace_g1 = MyValuedCard::<G1>::new(Ace);
let king_g1 = MyValuedCard::<G1>::new(King);
// Game G2
let ace_g2 = MyValuedCard::<G2>::new(Ace);
let king_g2 = MyValuedCard::<G2>::new(King);
println!("ace <= king -> {}", ace <= king);
println!("ace_g1 <= king_g1 -> {}", ace_g1 <= king_g1);
println!("ace_g2 <= king_g2 -> {}", ace_g2 <= king_g2);
}
which results in:
Standard Error
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.86s
Running `target/debug/playground`
Standard Output
ace <= king -> true
ace_g1 <= king_g1 -> true
ace_g2 <= king_g2 -> false
In this case, the user of your crate is able to change the order of your cards by defining a type that implements MyOrder.
But have I well understood your question?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.