Memory leak in implementation
The Function and Environment structs in the object module reference each other and have circular references. I'm using Rc here to make things work, but I have to figure out a way to clean all these things up after calls to Monkey functions happen.
The Monkey language has closures which cause a new environment to be created for the function, but an outer environment to be referenced.
I'm not quite sure how to structure this to clean things up. In the book, Thorsten glossed over this and relied on Go's garbage collector to do the work. Does this mean that I'll have to implement a GC for the language to get all of this to work without memory leaks?
Without further reading and research I've hit the limits of my knowledge so any ideas or pointers would be greatly appreciated.
From what I can tell the monkey language is garbage collected, and so you would need to implement a garbage collector/cycle detector in order to interpret it.
A simple GC should be easy for an interpreted language, as you should already know whats on the stack/in globals (the roots) and your data structures are easily traceable, so you just need to start from the roots, find everything reachable from them, and free anything you didn't encounter.
If you know you're going to be having back-pointers, I suggest taking a look at std::rc::Weak. Weak pointers allow access to a reference counted object without forcing it to stay alive, making them well suited for things like doubly-linked lists and parent pointers in a tree structure.
@boomshroom I tried Weak at some point, but I had a tricky thing where I didn't have a least one strong reference so it dropped everything while the closure still needed to be around. I should take another look at that to see if I can make it work.
Using Weak can't work in general for the language, because it has no clear ownership tree: functions own (should keep alive) their environment, and environments should own their functions. You can't even split them, because it's possible for functions to capture the same environment which owns them. You either have to introduce restrictions to the monkey language so that this is no longer possible, or implement some form of GC.
A big rust contributor has recently been experimenting with garbage collected pointers written in rust--- it's not production ready yet; but if you can't use it directly it might at least provide inspiration.
It provides a Gc type which is a substitute for Rc in most cases.
Repo: https://github.com/withoutboats/shifgrethor Blog: https://boats.gitlab.io/blog/post/shifgrethor-i/
I recently finished implementing monkey in Rust myself as well. (https://github.com/pandulaDW/interpreter-in-rust). And from the look of it, I've taken an approach somewhat similar to yours. Sorry for my ignorance, but shouldn't the new environments that gets created during function calls be dropped at the end of each call evaluation?, which in turn would also drop the RC references attached to its outer environments?
@pandulaDW It's been years since I thought about this code, but I think what I was referencing was that if you had a long running Monkey script, it would leak memory. If you just fire one-off Monkey scripts within a long running Rust process, then yes, that would be freed once the individual script invocation is done (I think).