Is it possible for a macro to take a constant expression and "inline" it to generate a valid LiteralPattern?

Viewed 384

I would like to pattern match a complex constant based on one of its fields. You can match on a constant using a PathPattern, but selecting a constant field of another constant is not allowed.

Is it possible to write a macro take the result of a constant expression and compile time "inline" it such that the generated code is a valid LiteralPattern?

Consider this working code:

pub struct Instruction {
    code: u8,
    width: usize,
}

pub const CMP_REG_U8: Instruction = Instruction { code: 3, width: 3 };
pub const CMP_REG_U32: Instruction = Instruction { code: 4, width: 6 };
pub const INVALID: Instruction = Instruction { code: 0, width: 0 };

fn main() {
    let opcode = 3u8;
    
    let inst = match opcode {
        3 => CMP_REG_U8,
        4 => CMP_REG_U32,
        _ => INVALID,
    };

    println!("inst.code: {} inst.width: {}", inst.code, inst.width);
}

I'd like to be able to write essentially this:

let inst = match opcode {
    CMP_REG_U8.code => CMP_REG_U8,
    CMP_REG_U32.code => CMP_REG_U32,
    _ => INVALID,
};

This way I don't have to put magic numbers in my match statement, nor repeat them a second time.

I do realise I can use guards:

let inst = match opcode {
    x if x == CMP_REG_U8.code => CMP_REG_U8,
    x if x == CMP_REG_U32.code => CMP_REG_U32,
    _ => INVALID,
};

...but this seems a little verbose and I'm concerned about how to convince the compiler to give me a jump table implementation. (Imagine instead of just returning the instruction, we want to execute a snippet of code and be able to reference the associated data like inst.width with no performance penalty compared to hardcoding the value as a literal.)

So bottom line, is it possible to solve this with a macro that "literalises" a constant?

let inst = match opcode {
    as_literal!(CMP_REG_U8.code) => exec_op(CMP_REG_U8),
    as_literal!(CMP_REG_U32.code) => exec_op(CMP_REG_U32),
    _ => INVALID,
};

This way the match arms would all be hardcoded integers which should make it super easy for the compiler to inline whatever exec_op does, recognising width and so forth as constants in that context, and do a computed goto or whatever to go there.

I've heard macros in Rust only allow you to do things you could have typed yourself. However, I could certainly have typed 3 instead of CMP_REG_U8.code -- the question is if Rust can do it for me. It would need to solve this at compile time for the generated pattern to be valid in a match.

1 Answers

You can use one macro to generate another:

macro_rules! define_consts {
    ($(
        $vis: vis const $name: ident : Instruction = Instruction { code: $code: literal, width: $width: literal };
    )*) => {
        $(
            $vis const $name: Instruction = Instruction { code: $code, width: $width };
        )*
        
        macro_rules! as_literal {
            $(($name . code) => { $code });*
        }
    }
}

Calling this with your constant definitions:

define_consts! {
    pub const CMP_REG_U8: Instruction = Instruction { code: 3, width: 3 };
    pub const CMP_REG_U32: Instruction = Instruction { code: 4, width: 6 };
    pub const INVALID: Instruction = Instruction { code: 0, width: 0 };
}

Will produce the constants as-written, and a macro as_literal!, that does what you want:

fn exec_op(inst: Instruction) -> Instruction {
    inst
}

fn main() {
    let opcode = 3u8;
    
    let inst = match opcode {
        as_literal!(CMP_REG_U8.code) => exec_op(CMP_REG_U8),
        as_literal!(CMP_REG_U32.code) => exec_op(CMP_REG_U32),
        _ => INVALID,
    };

    println!("inst.code: {} inst.width: {}", inst.code, inst.width); // 3
}

I'm not sure how valuable this is though! Given that the patterns are all literals, using if...else if... instead of match will likely produce the same assembly in the end.

Related