I have some code that uses a tree of widgets stored in a slotmap. Well, it's technically a graph, not a tree, because I can walk up to parent: Option<WidgetId> or down to children: Vec<WidgetId>. These properties are stored in the map indexed by WidgetId. I also have event handler closures floating around that have WidgetIds captured in them. And there are some secondary maps associating data to these widgets.
But I'm currently just leaking memory. I come from languages that have garbage collectors, so I've never really dealt with code like this before. I feel like I'm missing some kind of toolkit of techniques, and/or vocabulary, for dealing with it. Is this simply good-old manual memory management, where I need to think through when the app is done with a widget and at that point call remove on the slot maps?
You can unload a burden of memory reclamation on the end users. If they create a widget, they are responsible to remove it whenever they want to using WidgetId. For many tasks it's not so hard to do it by hand. For example, HashMap or SlotMap does not have any built-in garbage collection mechanism too. Manual entries management is an implied design feature.
Second, make a WidgetId a pair of integers: 1) The actual index into the inner SlotMap 2) A globally unique monotonically increasing number (Graph's Version or Generation). Whenever you issue a new key it is always associated with the version of the Graph. And the actual widget's object you are inserting into the widget's graph should also store this number. Such that whenever the user attempts to dereference or to remove the widget by WidgetId an inner implementation compares WidgetId's version with the version in the SlotMap. And if they mismatch you can return descriptive message indicating that the WidgetId obsolete.
This may happen, for example, if the user created a widget, then removed it, and then created a new one, but the SlotMop reuses the same index and therefore the old outdated WidgetId points to a new widget, which it should not. With unique version numbers this aliasing conflict should never happen by design.
Also notice that you can optimize the version's mechanism by increasing this number only in between of series of removals/insertions, which you can track using a single global flag. Hence, saving version numeric space.
Last but not least, it is also might be not so bad idea to just never clean up memory. Or to clear the entire graph altogether from time to time. Memory arenas with bump allocators is a real thing.
Upd: I just realized that SlotMap already have this mechanism with unique versions. Sorry, I confused it with Slab.
Probably so, yes. Unless there's some point you can throw out the whole slot map and start over or such.
Assuming you're using the plain SlotMap, it never shrinks its allocation, so it's size will reflect the maximum number of occupied slots over the course of the program. If you remove items, it will be able to reuse their slots for newly inserted items. Some of the other variations may reduce their size if you remove enough items.
Regardless of which variation you use, if you never remove items and only add them, it won't delete them for you (that would be a garbage collector). If you're dynamically adding items (as opposed to some fixed number for the course of the program) and never removing them (and never getting rid of the map as a whole), that would indeed be a memory leak.