[Newbie] Having difficulties with traits and implementations

Hello, im trying to make poker evaluation library.

trait DeckSizeRules {}

struct Deck52 {}
impl DeckSizeRules for Deck52 {}

struct Deck36 {}
impl DeckSizeRules for Deck36 {}

trait CardGameRules {
    type DeckSize: DeckSizeRules;
    // more rules
}

struct Holdem {}
impl CardGameRules for Holdem {
    type DeckSize = Deck52;
}
struct ShortDeckHoldem {}
impl CardGameRules for ShortDeckHoldem {
    type DeckSize = Deck36;
}

// more games added by library users impl CardGameRules

Ok, I want to to implement common Eval trait based on CardGameRules and having difficulties. My question is what is right way to split Eval implementation for Deck52 and Deck36

I've tried

trait Eval1 {
    fn eval() -> u32;
}

impl<T> Eval1 for T
where
    T: CardGameRules<DeckSize = Deck52>,
{
    fn eval() -> u32 {
        52
    }
}

impl<T> Eval1 for T
where
    T: CardGameRules<DeckSize = Deck36>,
 ^^^^^^^^^^^ conflicting implementations
{
    fn eval() -> u32 {
        36
    }
}

And

trait Eval2 {
    type DeckSizeEval: DeckSizeEval<Self>;
    fn eval() -> u32 {
        Self::DeckSizeEval::eval()
    }
}

impl<T> Eval2 for T
where
    T: CardGameRules,
{
    type DeckSizeEval = T::DeckSize;
 ^^^^^^^^^^^ the trait `DeckSizeEval<T>` is not implemented for `<T as CardGameRules>::DeckSize`
}

trait DeckSizeEval<T: ?Sized> {
    fn eval() -> u32;
}

impl<T> DeckSizeEval<Deck52> for T
where
    T: DeckSizeRules,
{
    fn eval() -> u32 {
        52
    }
}
impl<T> DeckSizeEval<Deck36> for T
where
    T: DeckSizeRules,
{
    fn eval() -> u32 {
        36
    }
}

At the end I want

    fn main() {
        assert_eq!(Holdem::eval(), 52);
        assert_eq!(ShortDeckHoldem::eval(), 36);
    }

I would use associated consts:

trait DeckSizeRules {
    const SIZE: usize;
}

struct Deck52 {}
impl DeckSizeRules for Deck52 {
    const SIZE: usize = 52;
}

struct Deck36 {}
impl DeckSizeRules for Deck36 {
    const SIZE: usize = 36;
}

trait CardGameRules {
    type DeckSize: DeckSizeRules;
    // more rules
    fn eval() -> usize { Self::DeckSize::SIZE }
}

Why don't you just follow the compiler's suggestion? Your approach #2 can be trivially made compile by adding the single missing bound T::DeckSize: DeckSizeEval<T>, which you specified in the trait definition, but for some reason left out from the impl (why?). Playground.

Btw, if I add the desired fn main() to the above playground, it becomes apparent that among all these levels of indirection, you confused the role of the type parameter of the trait DeckSizeEval. In one place, it looks like it should be the game type, in another, it seems you want it to represent the deck type.

Adding explicit impls for DeckSizeEval<Holdem> etc. makes the code compile and pass the assertions, but that's probably not what you wanted, given that you likely need generic blanket impls so that user-defined games get those impls, too.

I have a feeling that you simply have too many traits and that some of them should simply be removed; reasoning across multiple levels of blanket impls is hard, and you should probably be using associated types instead.

Cause im idiot :frowning: I've tried to follow compiler instruction but put it into wrong place.

Well, i'd like to see how i can use associated types in that case. There are many game types and idea is that library user can add something like IrishPoker, add CardRules and Eval for IrishPoker is ready.
Also, in reality eval is heavy, it uses huge tables for hand type recognition. So, if we use only Holdem and Omaha in final product, we don't want ShortDeck eval code linked.

Would this suffice?

Thank you for this one! It realy helps.

No, i think i messed up by putting 52 and 36 as return values in example code. I was trying not to add additional complexity. In reality ...

fn eval(cards: &[Card]) -> HandRank {...}

Well OK. Then what do you need from the Deck associated type and trait? If you don't need them, then you can (and should, probably) just remove the bound.

But DeckSize is important? I.e. eval("A6789") is Straight in ShortDeckHoldem (called wheel), but in standard Holdem it's 'Nothing'.

Unfortunately, if i add

fn main()
{
    assert_eq!(Holdem::eval(), 52);
}

It's another error:

error[E0599]: the function or associated item `eval` exists for struct `Holdem`, but its trait bounds were not satisfied

Adding playground with more clarification.
playground

Again, you are confusing the roles of the type parameter of DeckSizeEval. You are writing impls like DeckSizeEval<Deck36> but the thing that is actually supposed to impl Eval (which is apparently the game object itself) requires that T::DeckSize: DeckSizeEval<T>, i.e., the type parameter is now the game object, no the deck type. This doesn't compile for exactly the same reason as the previous playground I linked to.

You are saying that both the deck size and the particular cards are important (which I do get), but none of your examples so far actually use both of them at the same time. This doesn't help at all to guess what you are trying to do and how the two pieces of information are supposed to work together.

If you merely want to delegate evaluation from the game object to the deck, then you can do this.

1 Like

The goal im trying to achieve: Eval implementation for different CardGameRules. DeckSizeEval it's just my attempt to split implementations for different deck sizes. I just started learning Rust and I'm sure there is more clever way to make it work.

Again, if you are trying to forward to the Deck then you can check my link above. If not, please elaborate as to what exact, specific function the Deck has, and precisely what pieces of information you need from it in order to implement CardGameRules::eval().

CardGameRules::eval() want to know DeckType and choose implementation of eval at compile time.

That's exactly how my playground above works.

Yes, idea with 'delegation' is fine. But there are many other rules, not just DeckSize. I.e. CardGameRules::eval() want to know DeckType, IsHiGame, 'IsLoGame, 'IsHiLoGame, IsTripsBeatStraight and so on... and choose implementation of eval at compile time. I thought if i can split implementations for DeckSize then i can split for all.

And what exactly prevents you from doing that? You can always add more associated types and call methods on them.