Converting Errors to ControlFlow::Break

I've been playing around with the idea of writing an embedded extension language that doesn't ever call host functions directly. Instead, invoking a host function traps the virtual processor and returns control to the host, which is then free to directly alter the processor's internal state.

The basic structure would look something like this, but I'm not particularly happy with some of the ergonomics. In particular, return ControlFlow::Break(Trap::Err(anyhow!(...))) feels too verbose for what should be a common operation; is there a way to use ? for this?

Alternatively, would I be better off returning something like Option or Result instead of ControlFlow?

impl Processor {
    fn step(&mut self) -> ControlFlow<Trap> {
        let instr = match self.code.get(self.pc) {
            Some(i) => i,
            None => return ControlFlow::Break(Trap::Err(anyhow!("PC out of range: {}", self.pc))),
        };

        self.pc += 1;

        return match instr {
            Instr::PushAtom(x) => {
                self.stack.push(x.clone());
                ControlFlow::Continue(())
            }
            Instr::Halt => ControlFlow::Break(Trap::Halt),
            Instr::SysCall(which) => ControlFlow::Break(Trap::SysCall(*which)),
            Instr::Pop => match self.stack.pop() {
                Some(_) => ControlFlow::Continue(),
                None => ControlFlow::Break(Trap::Err(anyhow!("Stack Underflow"))),
            },
            // etc. ...
        };
    }

    fn run(&mut self) -> Trap {
        loop {
            match self.step() {
                ControlFlow::Break(trap) => return trap,
                ControlFlow::Continue(_) => (),
            }
        }
    }
}

The following blog post mentions a very cool idea which I may implement this very week-end unless somebody beats me to it:

To have an attribute macro to replace <expr>? with macro!(<expr>), so as to easily be able to perform that switch of "try" semantics :ok_hand:

2 Likes

In the meantime, that prompted me to define some local macros which make a big improvement:

    fn step(&mut self) -> ControlFlow<Trap> {
        macro_rules! trap_err {
            ($($tt:tt)*) => { return ControlFlow::Break(Trap::Err(anyhow!($($tt)*))) }
        }
        
        macro_rules! trap {
            ($($tt:tt)+) => { return ControlFlow::Break(Trap::$($tt)+) }
        }
        
        let instr = match self.code.get(self.pc) {
            Some(i) => i,
            None => trap_err!("PC out of range: {}", self.pc),
        };

        self.pc += 1;

        match instr {
            Instr::PushAtom(x) => {
                self.stack.push(x.clone());
            }
            Instr::Halt => trap!(Halt),
            Instr::SysCall(which) => trap!(SysCall(*which)),
            Instr::Pop => {
                if self.stack.pop().is_none() {
                    trap_err!("Stack Underflow");
                }
            },
        };
        
        ControlFlow::Continue(())
    }

Since you said "playing around", maybe use nightly to try out making your own type that can be ?ed via 3058-try-trait-v2 - The Rust RFC Book?

You might be inspired by https://docs.rs/rocket/0.4/rocket/outcome/enum.Outcome.html#impl-Try, which does something similar.

1 Like