Rc<Foo>, but Foo needs context to drop/cleanup?

I have a tree of widgets that I'm currently managing with Rc. I wanted to experiment with SlotMap and put some things connected to these objects in a central owner/context (struct Window), but then I wonder how to cleanup. With plain Rc, when the last ref is dropped, the memory gets cleaned up. But what if my Window has a field like:

struct Window {
    ...
    things: SlotMap<WidgetId, Stuff>

}

I'd need to somehow get an &mut Window in the drop method for my widgets, so I could remove the entry in the SlotMap.

Is there a pattern for handling this?

Well, the slopmap and a tree are for different things, a tree is for search fastly and slopmap is for cache optimization, you don't need to erase a slopmap. The objective is that the data don't change his address.

Well I need to deallocate the data because the Widgets come and go and eventually I'll run out of memory if I just leave their stuff in the SlotMap.

I'd like to help, but your question is quite vague. Can you please specify exactly what types you have, what do you store in an Rc and what do you want to do on drop/cleanup?

The objective is store the data together. The slotmap is for avoid using pointers. In this case you are using the heap for store your data, but when you are using slotmap you need manage your data not using the OS. A slotmap is a alternative of using an arena.

The types are UI Widgets. Sometimes they are Rc<Button>, Rc<Label>, etc. Other times Rc<dyn Widget>. The things I'm thinking of storing on the window are actually implementations of a trait GestureRecognizer. They come from widgets, but need to cooperate in a kind of shared space.

This does not make anything more precise. Can you create a minimal reproducible example, please? Most importantly can you provide proper definitions of all relevant types/traits and either show a compiler error you are getting, or describe with a comment what you want to do, but std does not allow you too?

One possible approach here is to use a MPSC channel — when dropped, the widget sends its own WidgetId on the channel, and the Window, the next time it is invoked and has a chance to manipulate things, reads IDs from the channel and deletes all entries from the SlotMap as specified.

This only works if it is acceptable to delay the cleanup, of course.

Another, perhaps clumsier option is to use Rc::downgrade to get a Weak ref you can put in Stuff. Then a later scan can check if there are any entries that don't have any strong counts and you can remove it. You might find that sort of approach easier to integrate, but I think the channel is cleaner.


Regardless, most windowing systems will let you post a "user message" that you can use to prompt a rapid response to cleaning up, but there's a risk here that large enough counts of widgets (why you might care about the type of map you're using) would swamp the message queue if enough are removed at once; there's a similar problem to using a channel, which you generally want to be bounded in case the receiver can't keep up.

You could perhaps combine a set of sync primitives to separately track the set of widgets to clean up from a single unprocessed cleanup request, but that seems pretty fiddly to get right.


If you're already in Rc world, you could instead try wrapping the Window in an Rc too, and hand a (weak!) ref to the contained widgets to use in their Drop. You'd need to add some interior mutability to the Window though, so this gets really messy quickly - perhaps this could provide a cleaner option to implement deferred cleanup than going through the UI message queue though.


In general the pattern you're looking for is called a "weak map", where you normally use the object itself as a key. For reference, here's a random library search result for "weak map", given I've not used any to be able recommend one: weak-table — data structures in Rust // Lib.rs

With a library doing the heavy lifting there you don't need any special handling to ensure Stuff gets dropped too, but they tend to be pretty memory inefficient.


The perhaps more "Rusty" approach would be to ditch Rc for widgets and handle the reference counting yourself, so you can control what happens. Perhaps a "WidgetRef" is a WidgetId and a Weak to a "WidgetSet" that maps ids to widget data (including reference count). Then you can directly integrate whatever additional context you like there, such as a set of Windows that get notified.

This is obviously a lot more work, though.

You don't get out of memory because slotmap is just a chunk of memory, you can erase when you want, you control that memory. A slotmap is just a way to access, modify, etc. that memory. But much more efficient that using the heap. What I trying to say is using slotmap, isn't the better for your case.

Thanks, lots of ideas. I've put a lot of work into this code, so doing something like a custom smart pointer someday is not out of the question, but I'll probably hold of on that for now.

you don't have a clear ownership model, that's what makes it confusing.

if the Stuff associated with each widget must be tied to the widget itself and needs to be dropped with the widget, then they are part of the widget themselves, and should be owned by the widget.

if the Stuff is owned and managed by Window, then the window should also manage the lifecycles of the widgets too, and when a widget is not needed, the windows removes the widget and its associated Stuff at the same time. in this scenario, the widget should know nothing about the associated data, it only releases the gui resource of its own on drop.


it feels like to me that your data structure is not tree-shaped, but more graph like (presumably a DAG, othereise you'll need Weak in addition to Rc), which is probably translated from some OO paradigm, where every "object" might have access to every other "object".

in the rust model, every value has an owner. tree-shaped data structures fit well in the ownership model, but not graphs. Rc and Arc allows "shared" ownership, but they hard to use for graph data structures

that's one of the impedance mismatch between rust and graph-like structures with direct node pointers. instead, rust programmers prefer indirect ids or handles as node identities when they need to work with graphs.

Thanks. Yes, I've been struggling and trying to learn about this issue with graphs, ownership, and smart pointers vs IDs for a while. I currently use Rc for my UI widgets, with no weak parent pointer. I have experimented with using IDs as well and may go back to it.

Part of why I abandoned the ID experiment, is that it required me to pass the owner around everywhere. Which felt so odd/ugly coming from Swift/Java world. However, I've evolved to have a near ubiquitous context argument anyway (basically &mut Window) for other reasons - event handlers need it to fire more events, or request re-draws, re-layouts, so now it might not be as inconvenient to pass around WidgetId instead of Rc<Widget>.

Yeah, I think of it as the ownership model in Rust pushing you to separate access in either "space" or "time", where space means you have clearly separate storage (eg. fields in a struct) for each part of your data and can statically show the compiler they won't conflict; and time means things like ensuring you don't access the actual storage until the last second, which often looks like using an id into a map.

The first is hard to get right the first time, and flies in the face of all OOP encapsulation, but tends to be very simple and clean when you get it right, the latter is very flexible and often surprisingly well performing but can lead to messy stuff like accidentally reimplementing allocators (or like in your example, a garbage collector)

The trade-off, of course, is that you get these problems as compile errors rather than crashes, so I can't be too salty about it!

I'm not sure what you mean there. Do you mean taking something that in normal OOP would be a single struct and breaking it up? Eg:

struct Thing {
   size: f32,
   color: Color
   ...
// becomes -->
struct SomeOwner {
   thing_sizes: Vec<f32>, // or keyed by some ID
   thing_colors: Vec<Color>

That's "struct of arrays" (SoA) which is often recommended for other non-OOP reasons, and can be a case that's easier in Rust, if you want to do something like:

thread::spawn(|| do_something_to_colors(&mut owner.thing_colors));
thread::spawn(|| do_something_to_sizes(&mut owner.thing_sizes));

But that is pretty rare outside "we have ECS at home".

Here as a simple example:

// just pass this to everything with all the guts hanging out...
pub struct Context {
  pub sender: Sender<Event>,
  pub windows: HashMap<WindowId, Window>
  pub widgets: Hashmap<WidgetId, Widget>,
  // ...
}

This is something in "proper OOP style" you would wrap up the internals with a send_event(&mut self, event: Event), get_/delete_/add_widget(&mut self, ...) etc., but in Rust that &mut self parameter "locks" the entire object, which makes simple code like this fail:

// remove all widgets that are contained in windows that don't exist any more...
for (id, widget) in context.iter_widgets() {
  if !context.has_window(widget.widget_id) {
    context.delete_widget(id); // conflicts with earlier borrows of `context`
    context.send_event(Event::WidgetDeleted(id)); // same
  }
}

With it all public, you can directly access the fields, and the compiler can see they don't conflict:

// Updating a collection while iterating is always a conflict, but the common cases
// have helper methods...
context.widgets.retain(|id, widget| {
  if context.windows.has(widget.window_id) {
     true
  } else {
     // not a conflict: borrows are for context.widgets, context.windows, etc....
     context.sender.send(Event::WidgetDeleted(id));
     false
  }
});

This is a simple case of "separating in space", another handy trick is using "double buffered" approaches to read from the previous state and writing to/returning the next state, or doing similar to what you have with the WidgetId map and keeping multiple "slices" of the same data separated by how the code uses them. Unfortunately, these are all a bit messy to demonstrate because they are inherently very tied to the logic that's accessing them.

Okay, thanks, now I see what you mean by "separating in space". Yes, I've been getting the hang of that some.

I don't like it because you arrange for one algorithm, but future algorithms could want something else. It feels a bit like de-normalizing data in a relational database.

I suspect Rust (or some language) will eventually want to extend at least private helper methods with some signature of how fields are used. I know there have been proposals for this. The overall goal is to prove that code is correct, at compile time. So eventually we'll probably need more fine-grained logic than: "You walked into the house, therefore I will assume you touched every object in the house".

I didn't think anyone's super happy about the current situation, but it started to make a bit more sense to me and be easier to "get it right" when I realized the standard OOP view on access is kind of mixing two different concerns: firstly maintaining internal invariants where it's actually necessary to maintain "internal" correctness for things like collections or synchronization types; and secondly to provide a known promised API surface for libraries. Having visibility control at the crate level allows you to separate these issues in a way most OOP languages struggle to.

But yes, you generally want to see if there's something more clever than a big fat Context object for your entire application, it's clumsy to work with if nothing else.