Generics, traits or macros?

⚓ Rust    📅 2025-06-30    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 37 days ago. The information described in this article may have changed.

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Generics, traits or macros?

Sorry for the vague title, trying to find an efficient way of doing something.
Optimization phase of a compiler, in the constant folding pass. I have a bunch of operations on a bunch of values. If both operands (for a binary op) are constants then the operation can be replaced a constant (int x = 4 + 5 for example). I can obviously make a huge set of multiply nested matches but it seems like I could simplify it

the values are

pub enum Value {
    Int32(i32),
    Int64(i64),
    UInt32(u32),
    UInt64(u64),
    Double(f64),
    Char(i8),
    UChar(u8),
    String(String),
    Variable(String, SymbolType),
    Void,
}

the first 7 are all constants of the given type. The binary operators are (I also have Unary ops)


pub enum BinaryOperator {
    Add,
    Subtract,
    Multiply,
    Divide,
    Remainder,
    BitAnd,
    BitOr,
    BitXor,
    ShiftLeft,
    ShiftRight,

    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,
}

so for example if the operator is Add and the Values are int32 I need to do (using EnumAsInner for the as_xxx calls)

let new_constant = Value::Int32(left.as_int32().unwrap().wrapping_add right.as_int32().unwrap()));

(Note the wrapping add)

I wrote this macro

macro_rules! binop_num {
    ($left:ident,$right:ident, $op:path) => {
        match $left.stype() {
            SymbolType::Int32 => {
                Value::Int32($op($left.as_int32().unwrap(), $right.as_int32().unwrap()))
            }
            SymbolType::UInt32 => Value::UInt32($op(
                $left.as_u_int32().unwrap(),
                $right.as_u_int32().unwrap(),
            )),
            SymbolType::Int64 => {
                Value::Int64($op($left.as_int64().unwrap(), $right.as_int64().unwrap()))
            }
            SymbolType::UInt64 => Value::UInt64($op(
                $left.as_u_int64().unwrap(),
                $right.as_u_int64().unwrap(),
            )),
            // SymbolType::Double => {
            //     Value::Double($op($left.as_double().unwrap(), $right.as_double().unwrap()))
            // }
            SymbolType::Char | SymbolType::SChar => {
                Value::Char($op($left.as_char().unwrap(), $right.as_char().unwrap()))
            }
            SymbolType::UChar => {
                Value::UChar($op($left.as_u_char().unwrap(), $right.as_u_char().unwrap()))
            }
            _ => panic!("Unsupported type for binop: {:?}", $left.stype()),
        }
    };
}

used like this

                            let result = match op {
                                BinaryOperator::Add => {
                                    binop_num!(left, right, WrappingAdd::wrapping_add)
                                }
                                BinaryOperator::Subtract => {
                                    binop_num!(left, right, WrappingSub::wrapping_sub)
                                }
                                ....

the problems are

  • doesnt compile for double, because double doesnt support wrapping add or bitwise ops
  • the logic ones are different - they need to return boolean or 0/1
  • the shift operations right hand are always int32 (for other operations left and right are the same type)

Any suggestions about the best way to do this, like I said I can simply type out enormous matches for each combination of Instruction (BinaryOperator, UnaryOperator,..), sub op (BinaryOperator::Add, Unary::Negate,..) and value type

1 post - 1 participant

Read full topic

🏷️ rust_feed