模仿 CraftingInterpeter 的 BINARY_OP 宏

Mimicking CraftingInterpeter's BINARY_OP macro

在尝试遵循优秀的 Crafting Interpreters book 但使用 Rust 而不是 C 时遇到了这个问题。

这本书构建了一个基于堆栈的虚拟机,其中我有一个简单的 Rust 版本,看起来像这样:

struct VM {
  stack: Vec<Value>
}

impl VM {
//...
pub fn interpret(op: OpCode) {
  match op {
    Op::Add => {
      let r = self.stack.pop();
      let l = self.stack.pop();
      self.stack.push(l + r);
    }
    // Repeated for -, *, and /
  }
}
}

这本书使用 C 风格的宏来防止 copy/pasting 所有 binop 使用相同的代码。

#define BINOP(op) \
    do { \
      double r = pop(); \
      double l = pop(); \
      push(l op r); \
    } while (false)

void interpret(VM *vm, OpCode op) {
  switch op {
    case OP_ADD: BINOP(+); break;
    case OP_SUB: BINOP(-); break;
    case OP_MUL: BINOP(*); break;
    case OP_DIV: BINOP(/); break;
  }

我尝试使用 Rust 宏做类似的事情

macro_rules! binop {
    ($op:tt) => {
        {
            let l = self.stack.pop(); 
            let r = self.stack.pop(); 
            self.stack.push(l $op r) };
    }
}

// ... inside VM impl ...

pub fn interpret(&mut self, op: OpCode) {
  match op {
    Op::Add => binop!(+)
    // Repeated for -, *, and /
  }
}

但是宏确实知道 self 是什么,我不确定让宏知道 self 的最佳方法是什么。

有没有办法在 Rust 中实现类似于 C 宏的东西?

您可以将 self 作为宏的参数传递:

macro_rules! binop {
    ($self:expr, $op:tt) => {{
        let l = $self.stack.pop(); 
        let r = $self.stack.pop(); 
        $self.stack.push(l $op r);
    }};
}

或者如果您在别处不需要它,您可以在内部函数定义宏:

pub fn interpret(&mut self, op: OpCode) {
    macro_rules! binop {
        ($op:tt) => {{
            let l = self.stack.pop(); 
            let r = self.stack.pop(); 
            self.stack.push(l $op r);
        }};
    }

    match op {
        Op::Add => binop!(+)
        // Repeated for -, *, and /
    }
}

另一种选择是使用闭包而不是宏:

impl VM {
    pub fn interpret(&mut self, op: OpCode) {
        match op {
            OpCode::Add => self.binop(|l, r| l + r),
            OpCode::Sub => self.binop(|l, r| l - r),
            OpCode::Mul => self.binop(|l, r| l * r),
            OpCode::Div => self.binop(|l, r| l / r),
        }
    }

    pub fn binop(&mut self, f: impl Fn(Value, Value) -> Value) {
        // Of course, in a real case you should handle unwrap:
        let l = self.stack.pop().unwrap();
        let r = self.stack.pop().unwrap();
        self.stack.push(f(l, r));
    }
}