[英]How should library crates be designed around Rust's Orphan Rule?
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
.impl
, Trait 或 Type 必须与impl
定义在同一个 crate 中。 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?所以问题是,应该如何设计一个库 crate,它提供了一个应该实现外部 trait 的类型,但让消费者 crate 来定义该 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.例如,想象一个假设的库 crate,
cards
,它提供了表示标准 52张扑克牌中的牌的类型,用于在外部 crate 中使用,以促进纸牌游戏程序的开发。 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.由于大部分流行的纸牌游戏都涉及比较 2 张纸牌的排名,因此
enum Rank
实现std::cmp::PartialOrd
应该是有意义的,以便可以轻松地将排名与<
、 >
、 <=
和>=
进行比较>=
运营商。 But crate cards
should not define this implementation because the ranks of playing cards are not intrinsically equipped with a partial ordering relation ;但是 crate
cards
不应该定义这个实现,因为扑克牌的行列本质上并不具备偏序关系; 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.例如,一些游戏认为
Ace
大于所有其他排名,而另一些游戏认为Ace
小于所有其他排名,某些游戏甚至可能根本不比较排名,因此不需要PartialOrd
实现。
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.为了与关注点分离的最佳实践保持一致,显然 crate
cards
不应该实现PartialOrd
,而是将其实现留给cards
的消费者,允许每个消费者定义他们自己的等级的偏序关系。 However, since in a crate depending on cards
, both Rank
and PartialOrd
are crate-foreign, this is prohibited by the Orphan rule.但是,由于在依赖于
cards
的 crate 中, Rank
和PartialOrd
都是 crate-foreign,因此 Orphan 规则禁止这样做。
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:在我看来,唯一正确的做法是避免
PartialOrd for Rank
并让消费者自己制作用于排名比较的函数,例如:
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 ):例如,您可以使用类型参数来定义排序( 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.在这种情况下,您的 crate 的用户可以通过定义实现 MyOrder 的类型来更改卡片的顺序。
But have I well understood your question?但是我充分理解你的问题了吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.