What is a good alternative to C's #define of global values

Hello,

I have a set of opcodes defined in C like this

#define JMP_OPCODE 0xC
#define JMPR 0x0
#define JMP 0x1

what is a good alternative to this in Rust. I was thinking to create enum but wanted to check maybe there is a more idiomatic way of doing this.

thank

You can just create global const values:

const A: u8 = 42;
const B: u8 = 100;

If you'd prefer to "bundle" them, most people do:

pub mod Opcode{
   pub const A: u8 = 42;
   pub const B: u8 = 100;
}

or

pub struct Opcode;

impl Opcode {
   pub const A: u8 = 42;
   pub const B: u8 = 100;
}

Both of these allow you to access the values as Opcode::A.

4 Likes

An enum with defined values would probably be the most idiomatic for this particular case.

6 Likes

Enums are also a good solution in general, but I don't agree that they would be idiomatic here.

You don't typically get opcodes as enum values, you get them as integers. Casting them to an enum and immediately back is useless.

Arguably opcodes are not an integer type. Even if they have what looks like integer values. One does not expect to be doing integer operations on them, +, -, *, / etc. Often not all possible integer values are used. Which makes me lean toward using enum.

5 Likes

My lack of enthusiasm about expressing them as an enum is mostly because I don't believe it is zero-cost (which I could be totally wrong about - I haven't profiled this).

For example, I personally have a lot of code that looks roughly like this:

fn deserialize(id: u32, file: Vec<u8>) -> Config {
    let mut buffer = Buffer::new(file);
    let mut config = Config {
        id,
        ..Default::default()
    };

    loop {
        match buffer.get_u8() {
            OPCODE::RETURN => {
                debug_assert_eq!(buffer.remaining(), 0);
                break config;
            }
            OPCODE::ONE => config.field_one = Some(buffer.get_u8()),
            OPCODE::TWO => config.field_two = Some(buffer.get_u16()),
            // etc..
            other => unimplemented!("Encountered unregistered opcode {}", other),
        }
    }
}

where I've used the struct+impl consts method above.

I could define a new .get_opcode() method on Buffer, that:

  • reads an u8
  • matches on the u8
  • returns the appropriate enum variant

and then I match on the enum instead. Would that be zero cost?