2
votes

I decided to implement a protocol which uses a couple of flags, so I started to define enums for the flags. However, when I want to define a flag that has two values which can be true or false I get an error message:

// The protocol definition says that the flag 
// can have two values true or false, so I could just use 
// plain bool, but I want another name for true and false.
enum Flag {
    ONE = true,
    TWO = false,
}
error[E0308]: mismatched types
 --> src/lib.rs:5:11
  |
5 |     ONE = true,
  |           ^^^^ expected isize, found bool

error[E0308]: mismatched types
 --> src/lib.rs:6:11
  |
6 |     TWO = false,
  |           ^^^^^ expected isize, found bool

The reason I want to use an enum instead of two constants is that the flag is not an bool. It is flag with representation value true or false, but I don't want to mix normal bools and flag. If I used bool constants, I could pass the flag value to every function that takes a bool as argument, or use them in expressions as bool, e.g.

if ONE {
}

fn some_function_with_a_flag(b: bool);
// I don't want this!
some_function_with_a_flag(ONE);

Using an enum instead of bool constants also prevents some more errors when using the flag as a struct member. There are more flags defined in the same way, so when I just use plain bools and constants I will have a struct like

struct Header {
    flag1: bool,
    flag2: bool,
    flag3: bool,
}

The compiler will accept code where the flag values are switched:

h = Header { flag3: ONE, flag1: TWO, flag2: ONE };

That is not possible when each flag is its own type (alias for bool).

The point of defining the enum with values true and false is just that the protocol defines it that way. In my code, I will probably only use the boolean value of the flags when data is packed to be serialized (it is part of the data header).

Ok, the compiler always assumes that the underlying type is isize. It could derive it from the values, but let's define it

#[repr(bool)]
enum E1 {
    ONE = true,
    TWO = false,
}
error[E0552]: unrecognized representation hint
 --> src/lib.rs:1:8
  |
1 | #[repr(bool)]
  |        ^^^^

It looks like I have to use u8 as the underlying type and then always cast the value into bool

#[repr(u8)]
enum E2 {
    ONE = 1,
    TWO = 0,
}

let x = E2::ONE as bool;

This compiles, but seems overly complicated. Is there a better way to define an enum with bool values? Is there an idiom for a type alias of bool where I can specify the value? I could just do

enum Flag {
    TWO, 
    ONE,
}

but now I again have to cast the value to bool all the time, and the order of definition looks unnatural.

Since the bool value will only be used when reading/writing the header I will just put the conversion into the corresponding functions and keep the rest of the program free from implementation details.

1
What's the reasoning for not just using a boolean since you want to cast to it?squiguy
"but now I again have to cast the value to bool all the time", well, that's just how Rusts enumerations work, and it has nothing to do with booleans: play.rust-lang.org/…mcarton
Can you explain why you want an enum that is not a bool, yet use it like a bool?Sebastian Redl
You don't need to specify #[repr(u8)] to get your enum represented as a single byte; the compiler already does that if the discriminants are in bounds, whether the discriminants are explicit or implicit.Francis Gagné
These two statements appear to be at odds: "The reason I want to use an enum instead of two constants is that the flag is not an enum and I don't want to mix normal bools and the flag." and "It looks like I have to use u8 as the underlying type and then always cast the value into bool [...] This compiles, but seems overly complicated." Do you want to be able to assign a bool to a Flag or not? If putting as bool is "overly complicated", then how could you remove that without making it possible to accidentally mix Flags and bools?trentcl

1 Answers

6
votes

No, you cannot use a bool as the underlying representation of an enum.

Instead, create constants:

const ONE: bool = true;
const TWO: bool = false;

You can also implement a method that converts the enum to a boolean:

enum Flag {
    One,
    Two,
}

impl From<Flag> for bool {
    fn from(f: Flag) -> bool {
        match f {
            Flag::One => true,
            Flag::Two => false,
        }
    }
}

Note that idiomatic Rust style uses UpperCamelCase for enum variants, and SHOUTING_SNAKE_CASE for constants.

I agree with commenters that it's strange to want to use an enum as a boolean. You can compare enums for equality or match on them:

if let Flag::One = flag {
    println!("something");
} else {
    println!("something else");
}
match flag {
    Flag::One => println!("something"),
    Flag::Two => println!("something else"),
}

In case that you are worried about size usage, note that a two-value enum (up to a 256-value enum) with no members is the same size as a boolean:

enum Flag {
    One,
    Two,
}

fn main() {
    use std::mem;
    assert_eq!(mem::size_of::<Flag>(), mem::size_of::<bool>());
}

See also: