A SlotMap has detach/reattach methods, to help with certain access patterns. With the SecondaryMapyou can just use remove/insert for a similar effect.
// &mut self
let x = self.things.remove(id).unwrap();
x.layout(self); // Now I can pass a &mut to things because x is not borrowed
self.things.insert(id, x);
If value removed is small, it doesn't matter, but if the value is bigger it might be nice to have a way to use the same idea, but without moving the object in memory. Would it be possible?
let x = self.things.detach_ref(id); // x is &Thing, or &mut Thing
x.do_stuff(self);
self.things.revive(id);
The slotmap would have to know that one of its values was off limits. Maybe something like this already exists. Maybe that is basically re-inventing RefCell....
The problem is that x.do_stuff(self) could mutate self in arbitrary ways (assuming it takes an &mut parameter), which might invalidate the whole self.things and hence x too.
When I call detach(id), presumably the SlotMap knows not modify itself certain ways, because it knows not to reuse that slot. It waits for the reattach. Why can't a similar thing be done for detaching and handing out a reference?
x.do_stuff(self) could completely replace the SlotMap with a different one, as well as several other things that would invalidate the reference. The SlotMap has no opportunity to stop those things, because they are things that happen to the ownership of the SlotMap, not calling SlotMap-specific functions.
I said this before but I try say different. You don't need to remove and then insert the object. Just use the data inside the slotmap, the problem is that you aren't saving the key, you need that key to access in the slotmap.
// &mut self
let x = self.things.remove(id).unwrap();
x.layout(self); // Now I can pass a &mut to things because x is not borrowed
let y=self.things.insert(id, x);
you want this operation to NOT move the value out of the slotmap, then what would the lifetime of the detached reference be, as shown in the '??? marker? the borrow checker only accept two options:
return a leaky 'static reference, which is unsound;
return a lifetime tied to the input slotmap 'a, in which case it just becomes Slotmpa::get_mut().
What would the signature for detach_ref look like? fn detach_ref<'this>(&'this self, id: K) -> Option<&'? V>... but what do you put in for the lifetime? You can't use 'this because then borrowck blocks your usage pattern, but you definitely can't use 'static.
EDIT: I see nerditation had the same thoughts as me, at the same time.
I think your are confusing something more important the propose of slotmap, it is save the data, like a Vec, and your are trying to safe references, that is posible but not the objective of using slopmap
The methods detach/reattach exist on SlotMap for a reason. There is a real use case for this. But I have to get to a class so don't have time explain now. Maybe later. I have a tree/graph of UI widgets. One of the things in my slotmaps is a Box that provides some customized behavior, like layout. And layout is recursive - it needs the slotmap again to get at data for other UI widgets...
the deeper problem is &mut in rust has very strong guarantees in the type system: it requires exclusiveness, not some weaker notion of "mutable". so what you have to do is change the signature of x.do_stuff(), so it does not take &mut Self. instead, it can take individual references of other fields that are disjoint with self.things.
this is a very common mistake, especially for users with OOP background. to demonstrate, suppose you have one large object (let's call it Parent), which "has-a" small object as a field (let's call it Child), it is impossible to call a Child method with a parameter of &mut Parent type on the field of a Parent object, because the child cannot borrow the parent exclusively, by definition:
struct Child {
//
}
impl Child {
fn do_stuff_with_parent(&mut self, parent: &mut Parent) {
todo!()
}
}
struct Parent {
child: Child,
//... other stuff
}
impl Parent {
fn do_child_stuff(&mut self) {
// IMPOSSIBLE to do in rust, reject by borrow checker
self.child.do_stuff_with_parent(self)
}
}
Are you also saying there should never be reason to to detach/remove an item from a SlotMap, call a function, and then reattach/insert the item again? (Like the code in the original post)
That seems unavoidable to me, for certain kinds of recursive algorithms on a tree. My tree has "parent" pointers, and some more experienced Rust devs recommended I use a SlotMap for this, and so far it seems to be working better than my previous attempts with Rc and rc::Weak.
no, I'm saying it's impossible to call a field's methods while passing &mut Self as argument: a child cannot borrow the parent exclusively.
in fact, if the API cannot be changed, the only way to make it work is you must (at least temporarily) move the child object out of the field of self, which is exactly what detatch/reattach is doing: moving the object ouf of the slotmap, which is a field.
it's simply impossible to "detach" without moving.
everyone has been telling you this isn't possible.
they are somewhat right, but also very wrong. this is possible, but with caveats.
what you need to do is have a different type, representing a mutable reference to a SlotMap with one or more detached elements.
this new types would allow certain methods on the SlotMap, and disallow others.
here is how it would look like.
there are potentially aliasing issues that would need to be ironed out, but nothing impossible.
the the code could look like
let things = self.things.detachable();
let x : &mut V = things.detach_in_place(id).unwrap();
// do stuff with_things and x at the same time
drop(things); // reattaches everything that was detached
but if you have a struct with a SlotMap field like things in your example, you can't borrow all the struct and x mutacbly at the same time as it would allow you to destroy the SlotMap while x exists
You'd have to jump some safety hoops to get detach_in_place to have the correct signature, and I'm not sure they're sound.
The naive version doesn't work. If you cast/transmute to change the lifetime, it seems fine on the surface (you check for membership before returning); however, I believe a malicious key type could cause unsoundness.