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!(),
};
}
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 implementClone
(even if the referent doesn't), while variables captured by mutable reference never implementClone
."