Someone asked about a specific functionality on Reddit around Christmas (described here), and I’ve been thinking about a possible implementation.
My plan/design:
Calling entry()
on a BTreeMap
gives a view into a single entry, but doesn’t make it possible to reach other key/value pairs from there. The main problem is, that the enty()
method mutably borrows the map, so similarly to a Vec
, a mutable reference to one of the elements locks the whole container. Despite these constraints, it is impossible to change the key in the Entry
after it was constructed (unless of course the tricks described in BTreeMap’s documentation are used, e.g. interior mutability). Every method in the Entry API, that causes modification of pointers in the B-tree consumes the Entry
object. These conditions make it possible to construct a method that gives mutable access to the Entry
object (so the key can be accessed and/or the value can be modified) and also gives access to the rest of the elements in the map. I called this mehod environment
(because it gives access to the surrounding elements/environment of the entry). This is probably not a good name but a couldn’t come up with a better one, maybe the native English speakers here can.
This is how it looks like (Self
is Entry
here):
pub fn environment<F>(self, mut f: F) -> Self
where F: FnMut(&mut Self, (RangeMut<K, V>, RangeMut<K, V>))
{
...
}
Basically, the user has to provide a closure that has two arguments:
- a mutable borrow to the
Entry
object - a tuple that contains two ranges: one for the preceding elements is the map, and one for the following elements (with intervals: [first, act) and (act, last])
Based on the intervals, this also works if the key is not in the map, it even makes it possible to split the map it two (or three, if the key is in the map) based on any provided key.
What do you think about this design? Do you think it has a place in the standard library? Can you find anything that is fundamentally wrong with it?
Implementation
I went through the source code of BTreeMap
and managed to understand the important parts. I tried to implement my method, first with everything mutable, then immutable, and 90% of the required stuff is already implemented, but the Handle
based design is intentionally restrictive to avoid dangling pointers, and I couldn’t figure out a way to implement it without modifying the internals of the codebase too much.
The algorithm: first we need to find the first leaf edge and the last leaf edge for the start of the first range and the end of the second range. If the Entry
is Occupied
, take the internal Handle
, turn it into a NodeRef
, run two range_search
es with the NodeRef
as the root of the search, one with (Unbounded, Excluded(entry.key()))
RangeArgument
, the other with (Excluded(entry.key()), Unbounded))
. These should give the correct positions for the two ranges, and searching is necessary, because ranges can only be constructed from Handle
s that point to an edge in a leaf node. And if the Entry
is Vacant
, the internal Handle
already points to the correct place and is good for both ranges.