Search code examples
rustenumscartesian-product

Get cross product of two enums in Rust without Typing them all out


Say for example we have two enums that represent the Rank and Suit of a deck of standard playing cards:

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Suit {
    Spades,
    Hearts,
    Diamonds,
    Clubs,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Rank {
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
    Ace,
}

enums are effectively bounded sets of values, and if one wishes to construct a complete deck of all the possible pairs we could model that as the Cartesian product of the two. In imperative terms one could image something like

for suit in Suit {
  for rank in Rank {
    Card { suit, rank }

however enums don't implement Iterator, which makes sense since enums can contain arbitrary data types including ones requiring parameters/construction mixed with ones that don't. I could implement the Iterator trait, or I could implement TryFrom with a tuple of integers and do for i in 0..4 { for j in 0..13 { or probably a few other things I could think of if I bothered, the problem as stated in the question title is that AFAIK all of those solutions would require me to type out all 52 variations by hand in the implementation.

I know there are several implementations of an iterable enum macro floating around for static enums, is there any other way to do this simply without typing them all out or using a procedural macro? Or am I just totally off base modeling a deck of cards this way?


Solution

  • As already linked, there are lots of ways to generate enum iterators, but you may be interested in my library exhaust, which can also generate a Cartesian product iterator for the struct (and for the values of fields within enum variants, but that does not apply to this situation):

    use exhaust::Exhaust;
    
    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Exhaust)]
    enum Suit {
        Spades,
        Hearts,
        Diamonds,
        Clubs,
    }
    
    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Exhaust)]
    enum Rank {
        Two,
        Three,
        Four,
        Five,
        Six,
        Seven,
        Eight,
        Nine,
        Ten,
        Jack,
        Queen,
        King,
        Ace,
    }
    
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Exhaust)]
    struct Card {
        rank: Rank,
        suit: Suit,
    }
    
    fn main() {
        for c in Card::exhaust() {
            println!("{c:?}");
        }
    }
    

    prints:

    Card { rank: Two, suit: Spades }
    Card { rank: Two, suit: Hearts }
    Card { rank: Two, suit: Diamonds }
    Card { rank: Two, suit: Clubs }
    Card { rank: Three, suit: Spades }
    Card { rank: Three, suit: Hearts }
    Card { rank: Three, suit: Diamonds }
    Card { rank: Three, suit: Clubs }
    Card { rank: Four, suit: Spades }
    Card { rank: Four, suit: Hearts }
    ...