2

So in Rust, i am trying to iterate over a vector of u8, and match them against cases of an enum. To do that, I set up the following code.

use std::io::prelude::*;

enum Byte {
    A = 0,
    B = 1,
    C = 2,
    D = 3
}

fn main() {
    let mut buf = Vec::<u8>::new();
    write!(buf, "\x01\x03\x02\x00");
    match buf[1] {
        Byte::A => println!("got A"),
        Byte::B => println!("got B"),
        Byte::C => println!("got C"),
        Byte::D => println!("got D")
    }
}

Assume the println! statements could be any form of behavior, such as determining what type comes next in the byte vec, etc.

However, this results in

error[E0308]: mismatched types
  --> src/main.rs:16:9
   |
6  |     A = 0,
   |     ----- unit variant defined here
...
15 |     match buf[1] {
   |           ------ this expression has type `u8`
16 |         Byte::A => println!("got A"),
   |         ^^^^^^^ expected `u8`, found enum `Byte`

Then, if i try to cast the enum as a u8, with Byte::A as u8 , a syntax error occurs. I am aware it's possible to convert the u8 into a Byte, then match the converted Byte, but that requires two match statements, one to convert, then one to compare. I am looking for a way similar as in C/C++, to treat an enum case directly as an integer in Rust. Such that my original code can function.

If there is no way to do this, a way to match against named constants in a match statement would suffice.

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38
  • Why do you need the enum ? You can match directky in the integer – Svetlin Zarev Aug 06 '21 at 08:18
  • `C/C++` mean nothing choice between C or C++ – Stargateur Aug 06 '21 at 08:34
  • "I am looking for a way similar as in C/C++" This use of Rust `enum` is more like C++ `enum class`, which _can't_ be treated directly as an integer and has to be converted first. The equivalent C++ code would fail to compile too. If you want to get the equivalent of C `enum`, just use named constants. – Alexey Romanov Aug 06 '21 at 09:21

1 Answers1

4

In short: you want to convert1 an u8 value to an Byte's variant.

The problem is that there are values of u8 that don't correspond to any variants of the Byte enum, for example, any u8 value higher than 3 doesn't have a variant in Byte backing it (i.e., any u8 value that matches 4..=255).

How should such a conversion behave if you (try to) convert 7u8 to a Byte's variant? Converting an u8 value to an enum's variant falls into the category of fallible conversions.


You could however implement the trait TryFrom<u8> for Byte for this kind of fallible conversion:

use std::convert::TryFrom;

impl TryFrom<u8> for Byte {
    type Error = ();

    fn try_from(val: u8) -> Result<Byte, ()> {
        match val {
            0 => Ok(Byte::A),
            1 => Ok(Byte::B),
            2 => Ok(Byte::C),
            3 => Ok(Byte::D),
            _ => Err(()),
        }
    }
}

Its associated function try_from() returns a Result<Byte, ()> instead of just Byte because the conversion may fail. Then, you just need extract the Byte (if any) from the returned Result:

match Byte::try_from(buf[1]).unwrap() {
    Byte::A => println!("got A"),
    Byte::B => println!("got B"),
    Byte::C => println!("got C"),
    Byte::D => println!("got D")
}

Or more concise, had Byte implemented Display, then you could just do away with the match above:

println!("got {}", Byte::try_from(buf[1]).unwrap());

1 Note that performing a conversion in the other direction, i.e., from a Byte's variant to an u8 value, can be done trivially with the as operator, e.g., Byte::A as u8.

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • This still uses two match statements. Looking for a solution that only uses one match statement, or a different way to match against named constants. – Josh Weinstein Aug 06 '21 at 07:18
  • I see your point on that some `u8` does not correspond to `Byte`, but I am looking for a way to use a definite `Byte` value like `B` as the `u8` value `1`. Such as in C++ where an enum can be used as an integer. – Josh Weinstein Aug 06 '21 at 07:22
  • 1
    @JoshWeinstein What you want is the opposite: to use an integer as an enum. – JFMR Aug 06 '21 at 07:24
  • @JoshWeinstein the second `match` is superfluous, it's just for consistency with your code. You can just implement `Display` for `Byte` and do `println!("got {}", Byte::try_from(buf[1]).unwrap())`. You just need a single `match` for performing the conversion. – JFMR Aug 06 '21 at 07:26
  • The thing is the `println` statements are just an example, assume that the code for each case may be anything, like taking a slice of the next n bytes and interpreting them in some specific way, or other integer value dependent behaviors. If Rust allows you to put `A = 1, B= 2 ` in an enum definition, there should be some way to use the enum as those constant values. – Josh Weinstein Aug 06 '21 at 07:48
  • Like the logic I am looking for would be similar to `Byte::A == 4`, where `Byte::A` would be converted to an integer value. – Josh Weinstein Aug 06 '21 at 07:49
  • @JoshWeinstein converting a `Byte`'s variant to an `u8` value is trivial, e.g., `Byte::A as u8`. It's the other direction the one that may fail. – JFMR Aug 06 '21 at 07:51
  • `Byte::A as u8` does not work in a match statement, results in syntax error. – Josh Weinstein Aug 06 '21 at 07:58
  • 1
    @JoshWeinstein that's not how Rust enums work. Rust enums are type-safe sum types. What you want is named constants, that's what C enums are. – Masklinn Aug 06 '21 at 08:07
  • "If Rust allows you to put A = 1, B= 2 in an enum definition, there should be some way to use the enum as those constant values." there is, you can convert enum variants to the assocated value, not the other way around. – Masklinn Aug 06 '21 at 08:08
  • Incidentally [there is an RFC and an open issue for "const expression patterns"](https://github.com/rust-lang/rust/issues/76001) so on nightly you can use `const {Byte::A as u8}` as a pattern, but it could also crash your compiler so YMMV. Alternatively you can use [the inline-const crate](https://crates.io/crates/inline-const) to kinda sorta emulate this feature using macros. Otherwise you can in fact define e.g. `const A: u8 = Byte::A as u8`, and use *that* as a pattern, though the convenience is debatable. – Masklinn Aug 06 '21 at 08:14
  • There are crates that can generate the boilerplate for enum->integer conversions, e.g. [num_enum](https://crates.io/crates/num_enum). Perhaps that would be a solution if you'd like to avoid writing your own conversions. – justinas Aug 06 '21 at 14:44