简体   繁体   English

应该如何围绕 Rust 的孤儿规则设计库 crate?

[英]How should library crates be designed around Rust's Orphan Rule?

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 the impl .对于 Type 上的 Trait 的任何impl , Trait 或 Type 必须与impl定义在同一个 crate 中。

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.不可能在外部 crate 中定义的类型上实现在外部 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?所以问题是,应该如何设计一个库 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 中, RankPartialOrd都是 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM