4

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.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • 1
    What's the reasoning for not just using a boolean since you want to cast to it? – squiguy Sep 27 '18 at 22:08
  • 1
    "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: https://play.rust-lang.org/?gist=ded9c99eae954aee741a4808474706b4&version=stable&mode=debug&edition=2015 – mcarton Sep 27 '18 at 22:09
  • Possible duplicate of [Enums with constant values in Rust](https://stackoverflow.com/questions/36928569/enums-with-constant-values-in-rust) – Artemij Rodionov Sep 27 '18 at 22:17
  • https://play.rust-lang.org/?gist=1ec26f17ba8a9e4980d03a6c44a9207e&version=stable&mode=debug&edition=2015 – Artemij Rodionov Sep 27 '18 at 22:28
  • 4
    Can you explain why you want an enum that is not a bool, yet use it like a bool? – Sebastian Redl Sep 27 '18 at 22:53
  • 1
    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é Sep 28 '18 at 00:20
  • "now I again have to cast the value to `bool` all the time" If these were integers instead, you'd still have to cast them, so it's not a problem specific to `bool`. – Francis Gagné Sep 28 '18 at 00:21
  • @squiguy I've edited the question. I don't want to cast it all the time, just when it is explicitly needed. That will probably only be the case when serializing/deserializing the header and the enum will be used all the time. That is the whole point of the enum anyway. – Jens Sep 28 '18 at 05:36
  • @SebastianRedl The point is that I don't want to use it like a bool. I want an enum that has two possible values, and when needed gives me the representation value that happens to be defined as true or false. – Jens Sep 28 '18 at 05:38
  • Probably relevant guideline: [C-BITFLAG](https://rust-lang-nursery.github.io/api-guidelines/type-safety.html#c-bitflag) – E_net4 Sep 28 '18 at 09:51
  • 1
    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 `Flag`s and `bool`s? – trent Sep 28 '18 at 14:21
  • @trentcl The statements are add odds because there was a little mistake in the first one. It should be "that the flag is not an bool". I've corrected it. I came to the conclusion that using `as bool` actually describes pretty much the intention of converting the flag to (or from) `bool` when needed. However, I don't like the fact that I have to assign numeric values which are then automatically converted to `bool` due to the fact that the enum is restricted. I think implementing `From for bool` as suggested in the answer is a more or less literal translation of my intention. – Jens Sep 28 '18 at 18:35
  • @E_net4 Thanks for the hint to look into `C-BITLFAG`. It will certainly be useful. – Jens Sep 28 '18 at 18:36
  • I'm (pleasantly) surprised that `E2::ONE as bool` works at all; I would have expected to have to write `E2::ONE as u8 as bool` or something. – trent Sep 28 '18 at 18:40

1 Answers1

8

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Minor nitpick: You say "You can compare enums directly as well as matching on them" and then provide two examples of matching on them rather than a comparison and a match. I'd change the first example to a comparison. – Cubic Sep 28 '18 at 10:05
  • @Cubic I really had meant to just link to the existing Q&A for that bit. Thanks for reminding me! – Shepmaster Sep 28 '18 at 13:07