1

I'm writing a piece of Rust code that simulates a small (typed) toy language with closures, but I'm not sure how I should store them in an enum so that they are still able to access/use a common heap when I evaluate the closures.

I'd like the program's data to be stored on a heap, and I allow all of the closures to take in this heap as an argument in case it needs to allocate something, retrieve a value, etc. I'm okay if the closure code itself is dynamically allocated by Rust, but I want the pointers to the closures themselves to be in my Heap struct.

I've tried storing the data as a function pointer (like fn(usize, usize, &mut Heap)), as well as boxed closures (like Box<dyn FnMut(usize, &mut Heap) -> usize>), as well as a boxed pointer version with a lifetime argument (like Box<dyn FnMut(usize, &mut Heap) -> usize + 'a>) but I always seem to run into some issue with the borrow checker.

Here is a simplified version of my code:

enum Val {
    Int(i32),
    Bool(bool),
    Lambda(Box<dyn FnMut(usize, &mut Heap) -> usize>),
}

struct Heap {
    mem: Vec<Val>,
}

impl Heap {
    fn alloc(&mut self, v: Val) -> usize {
        self.mem.push(v);
        self.mem.len() - 1
    }

    fn get(&self, i: usize) -> &Val {
        &self.mem[i]
    }
}

fn apply(func: usize, arg: usize, heap: &mut Heap) -> usize {
    let closure = match heap.get(func) {
        Val::Lambda(x) => x,
        _ => panic!(),
    };
    closure(arg, heap)
}

fn main() {
    let mut h = Heap { mem: vec![] };

    // creating a closure
    let foo = Val::Lambda(Box::new(|a: usize, mut heap: &mut Heap| -> usize {
        let a_val = match heap.get(a) {
            Val::Int(x) => *x,
            _ => panic!(),
        };
        heap.alloc(Val::Int(a_val * a_val))
    }));
    let f = h.alloc(foo);

    // using the closure
    let a = h.alloc(Val::Int(3));
    let b = apply(f, a, &mut h);
    match h.get(b) {
        Val::Int(x) => println!("{}", x),
        _ => panic!(),
    };
}

Rust playground

The above code should output 9, but instead there is a borrow-checker error:

error[E0596]: cannot borrow `**closure` as mutable, as it is behind a `&` reference
  --> src/main.rs:27:5
   |
23 |     let closure = match heap.get(func) {
   |         ------- help: consider changing this to be a mutable reference: `&mut std::boxed::Box<dyn for<'r> std::ops::FnMut(usize, &'r mut Heap) -> usize>`
...
27 |     closure(arg, heap)
   |     ^^^^^^^ `closure` is a `&` reference, so the data it refers to cannot be borrowed as mutable

error[E0502]: cannot borrow `*heap` as mutable because it is also borrowed as immutable
  --> src/main.rs:27:5
   |
23 |     let closure = match heap.get(func) {
   |                         ---- immutable borrow occurs here
...
27 |     closure(arg, heap)
   |     -------^^^^^^^^^^^
   |     |
   |     mutable borrow occurs here
   |     immutable borrow later used by call

warning: variable does not need to be mutable
  --> src/main.rs:34:47
   |
34 |     let foo = Val::Lambda(Box::new(|a: usize, mut heap: &mut Heap| -> usize {
   |                                               ----^^^^
   |                                               |
   |                                               help: remove this `mut`
   |
   = note: #[warn(unused_mut)] on by default

Is it possible to clone my closures / enum to mitigate this borrowing issue? I am not sure if this is possible due to the restrictions on Clone listed by the Rust documentation:

"Closure types, if they capture no value from the environment or if all such captured values implement Clone themselves. Note that variables captured by shared reference always implement Clone (even if the referent doesn't), while variables captured by mutable reference never implement Clone."

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Christopher Rybicki
  • 369
  • 1
  • 2
  • 11
  • What you are attempting to do is inherently memory unsafe. You have a reference to something in a vector and potentially try to mutate it while that reference is outstanding. That could invalidate the reference. This is exactly what Rust prevents — introducing memory unsafety. – Shepmaster Apr 17 '19 at 18:24
  • Search on stack overflow for the error message you get and you'll find hundreds of questions about the same thing. – Shepmaster Apr 17 '19 at 18:27
  • Your immediate error is solved by [How can I pass a FnMut closure to a function using a reference in Rust?](https://stackoverflow.com/q/43069178/155423). Then you run into [Cannot borrow as mutable because it is also borrowed as immutable](https://stackoverflow.com/q/47618823/155423) – Shepmaster Apr 17 '19 at 18:29
  • Gotcha, thanks @Shepmaster. I'm familiar with the general borrow checking aspect, I was just trying to think if there was any way to reformulate my code that deals with the pass-in-heap part more safely. – Christopher Rybicki Apr 18 '19 at 21:50
  • For the time being I've fixed my code by changing my `Val::Lambda` to take in a function pointer instead, and make all of the functions/lambdas/closures at the top level (i.e. global). – Christopher Rybicki Apr 18 '19 at 21:52

0 Answers0