A really powerful design pattern is combining the use of handles and closures in a memory allocator. Here's a simplified Rust example:
let mut myalloc = MyAllocator::<Foo>::new();
let myfoohandle = myalloc.allocate();
let myfoohandle2 = myalloc.allocate();
myalloc.expose::<&mut Foo, &Foo>(myfoohandle, myfoohandle2, |myfoo, myfoo2| {
myfoo.do_mutating_foo_method();
println!(myfoo2);
});
myalloc.compact(); // Rearrange the allocated memory for myfoo and myfoo2
myalloc.expose::<&mut Foo>(myfoohandle, |myfoo| {
// Still valid!
myfoo.do_mutating_foo_method();
});
Internally, the allocator would use a map of handles to pointers to keep track of things.
Because the closures strongly limit the scope of the referenced memory, you can relocate the memory of actively used objects without fear of dangling references. This allows the allocator to perform memory compactions whenever the user wants (e.g. idle periods).
Not really. Compaction is a feature of many garbage collectors, but the allocator I described doesn't impose any particular way of deciding how and when to deallocate the objects. You could do so explicitly with a MyAllocator::deallocate() method, or use runtime reference counting, or (if you're willing to constrain handle lifetimes to the allocator lifetime) you could delete the allocator entry when the handle goes out of scope.
I kind of agree. A while ago I was thinking about how to implement something like Roslyn's red-green-trees in Rust. The solution I came up, with a similar in principle handle-and-allocator approach, did work, but would need the occasional cleanup, to get rid of zombie objects. At that point I realised that all I've done was reinvent a poor mans garbage collector.
Because the closures strongly limit the scope of the referenced memory, you can relocate the memory of actively used objects without fear of dangling references. This allows the allocator to perform memory compactions whenever the user wants (e.g. idle periods).