Any bijective match patter?

Hi all! I had an issue from my starts with Rust, the issue is not about rust, is related to match and biyective relations, by bijective I means, two groups with elements, one element of one group is always a match to only one of the other group, so you can move from the group A to B, and B to A always by the same element.

enum Word {
  A,
  B,
  C,
}

enum Number {
  Zero,
  One,
  Two,
}

fn letter2number(word: Word) -> Number {
  match word {
    Word::A => Number::Two,
    Word::B => Number::One,
    Word::C => Number::Zero,
  }
}

fn number_to_word(number: Number) -> Word {
  match number {
    Number::Zero => Word::C,
    Number::One => Word::B,
    Number::Two => Word::A,
  }
}

Bijective relations allow us to go from one to other in a clear way, they also have the full information for match, this is a simple case, but more complex we need to make a test to always prove the conversion Word -> Number -> Word is always the same to be consistent.

Is easier to make a table of relations for example, a const one, but that implies do not use match, but match is nice because if anything changes, rust will not compile until we handle the missed ones.

But maybe, there already exists a crate or other way to handle bijetive patters, to avoid issues of consistency.

Thx!

Should be doable with a declarative macro, I think:

use paste::paste;

macro_rules! bijective {
    ($ty_a:ty => $ty_b:ty; $($a:path => $b:path),*) => {
        paste! {
            fn [<$ty_a:lower 2 $ty_b:lower>](a: $ty_a) -> $ty_b {
                match a {
                    $(
                        $a => $b,
                    )*
                }
            }

            fn [<$ty_b:lower 2 $ty_a:lower>](b: $ty_b) -> $ty_a {
                match b {
                    $(
                        $b => $a,
                    )*
                }
            }
        }
    }
}

enum Word {
    A,
    B,
    C,
}

enum Number {
    Zero,
    One,
    Two,
}

bijective! {
    Word => Number;
    Word::A => Number::Two,
    Word::B => Number::One,
    Word::C => Number::Zero
}

Playground.

(I'd probably implement From<Number> for Word and From<Word> for Number instead of the number2word and word2number functions though.)

5 Likes

:open_mouth: that is a nice solution! I did not thought on directly implement on From, this issue could completely get rid off also using TryFrom for the panic!() cases.

come to think of, what is best when we make this type of implementations, return a reference or a owned value? we could return for example &Word::A instead of Word::A.

In this case, I'd be very specific that you can either return &'static Word or Word. This only works, because an expression like &Word::A can be promoted to &'static Word. This is called constant promotion. Unless there is some valid reason to do otherwise, I'd prefer returning an owned value instead of a reference.

2 Likes