Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the BTreeMap cursor API using gaps #118208

Merged
merged 3 commits into from
Jan 26, 2024
Merged

Conversation

Amanieu
Copy link
Member

@Amanieu Amanieu commented Nov 23, 2023

Tracking issue: #107540

Currently, a Cursor points to a single element in the tree, and allows moving to the next or previous element while mutating the tree. However this was found to be confusing and hard to use.

This PR completely refactors cursors to instead point to a gap between two elements in the tree. This eliminates the need for a "ghost" element that exists after the last element and before the first one. Additionally, upper_bound and lower_bound now have a much clearer meaning.

The ability to mutate keys is also factored out into a separate CursorMutKey type which is unsafe to create. This makes the API easier to use since it avoids duplicated versions of each method with and without key mutation.

API summary:

impl<K, V> BTreeMap<K, V> {
    fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
}

struct Cursor<'a, K: 'a, V: 'a>;

impl<'a, K, V> Cursor<'a, K, V> {
    fn next(&mut self) -> Option<(&'a K, &'a V)>;
    fn prev(&mut self) -> Option<(&'a K, &'a V)>;
    fn peek_next(&self) -> Option<(&'a K, &'a V)>;
    fn peek_prev(&self) -> Option<(&'a K, &'a V)>;
}

struct CursorMut<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&K, &mut V)>;
    fn prev(&mut self) -> Option<(&K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct CursorMutKey<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&mut K, &mut V)>;
    fn prev(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&mut K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct UnorderedKeyError;

Tracking issue: rust-lang#107540

Currently, a `Cursor` points to a single element in the tree, and allows
moving to the next or previous element while mutating the tree. However
this was found to be confusing and hard to use.

This PR completely refactors cursors to instead point to a gap between
two elements in the tree. This eliminates the need for a "ghost" element
that exists after the last element and before the first one.
Additionally, `upper_bound` and `lower_bound` now have a much clearer
meaning.

The ability to mutate keys is also factored out into a separate
`CursorMutKey` type which is unsafe to create. This makes the API easier
to use since it avoids duplicated versions of each method with and
without key mutation.

API summary:

```rust
impl<K, V> BTreeMap<K, V> {
    fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
}

struct Cursor<'a, K: 'a, V: 'a>;

impl<'a, K, V> Cursor<'a, K, V> {
    fn next(&mut self) -> Option<(&'a K, &'a V)>;
    fn prev(&mut self) -> Option<(&'a K, &'a V)>;
    fn peek_next(&self) -> Option<(&'a K, &'a V)>;
    fn peek_prev(&self) -> Option<(&'a K, &'a V)>;
}

struct CursorMut<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&K, &mut V)>;
    fn prev(&mut self) -> Option<(&K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V);
    fn insert_before(&mut self, key: K, value: V);
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct CursorMutKey<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&mut K, &mut V)>;
    fn prev(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&mut K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V);
    fn insert_before(&mut self, key: K, value: V);
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}
```
@rustbot
Copy link
Collaborator

rustbot commented Nov 23, 2023

r? @joshtriplett

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Nov 23, 2023
@ripytide
Copy link
Contributor

should insert_after(), insert_before() functions panic internally rather than return results? I think it would be more flexible for the caller if they can catch/handle invalid insertion rather than internally panicking. Although at the cost of making users not expecting invalid insertions .unwrap() when calling. To me the more explicit Result option seems more rusty.

Comment on lines +2937 to 2947
/// # Safety
///
/// Since this cursor allows mutating keys, you must ensure that the `BTreeMap`
/// invariants are maintained. Specifically:
///
/// This returns `None` if the cursor is currently pointing to the
/// "ghost" non-element.
/// * The key of the newly inserted element must be unique in the tree.
/// * All keys in the tree must remain in sorted order.
#[unstable(feature = "btree_cursors", issue = "107540")]
pub fn value_mut(&mut self) -> Option<&mut V> {
self.current.as_mut().map(|current| current.kv_mut().1)
pub unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A> {
self.inner
}
Copy link
Contributor

@finnbear finnbear Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm skeptical that maintaining BTreeMap invariants is safety critical because, if it was, then the following safe code would be unsound:

struct Key;
impl Ord for Key {
    fn cmp(&self, other: &Self) -> Ordering {
        [Ordering::Less, Ordering::Equal, Ordering::Greater].choose(&mut thread_rng())
    }
}
for _ in 0..3 {
    map.insert(Key, ());
}

And, from the docs, we have:

It is a logic error for a key to be modified in such a way that the key’s ordering relative to any other key, as determined by the Ord trait, changes while it is in the map...The behavior resulting from such a logic error is not specified, but will be encapsulated to the BTreeMap that observed the logic error and not result in undefined behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second, I don't see mutable being spelled out in any ident in std. It's always abbreviated to mut.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want that unsafe code can trust the order of BTreeMap<usize, _>. If you can get a &mut reference to a key you can swap it with a different value even if the type (like usize) is entirely well-behaved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok good to know! My point about the name still stands.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned in the tracking issue, I much prefer leveraging the entry API and adding key mutation there (see: #112896) rather than adding an entire other API like this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

@@ -2738,22 +2740,21 @@ impl<K, V> Clone for Cursor<'_, K, V> {
#[unstable(feature = "btree_cursors", issue = "107540")]
impl<K: Debug, V: Debug> Debug for Cursor<'_, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Cursor").field(&self.key_value()).finish()
f.write_str("Cursor")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a requirement to merge, but it would be nice if this also detailed where the cursor was relative to the elements in the map.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe by including the value from either side of the cursor, if any?

Cursor {
    prev: Some("m"),
    next: Some("n"),
}

#[unstable(feature = "btree_cursors", issue = "107540")]
impl<K: Debug, V: Debug, A> Debug for CursorMut<'_, K, V, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CursorMut")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here -- it could probably just reuse the same code.

@@ -3248,5 +3238,114 @@ impl<'a, K: Ord, V, A: Allocator + Clone> CursorMut<'a, K, V, A> {
}
}

impl<'a, K: Ord, V, A: Allocator + Clone> CursorMut<'a, K, V, A> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned on the tracking issue, I would much rather use the entry API with key mutation instead of coming up with an entirely different API mirrored on the cursor. Methods like these could potentially just return VacantEntry or OccupiedEntry to do insertion and mutation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Methods like these

I'm confused, which functions are you referring to?

I think it would be less confusing for both the entry API and the Cursor API to both have the core methods like mutating/changing entries rather than the Cursor API returning VacantEntry/OccupiedEntry which seems like it would make things quite messy as it adds an extra level of indirection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a downside to methods that consume self and return a new type: it makes it much harder to write a type which wraps a CursorMut. The methods on the wrapper type would take &mut self but you can't easily take ownership of the cursor if it is in a struct field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, although we could always just mutably borrow in these cases; the only reason why entries take values by self is because they actually contain values that are consumed. Since the lifetime is still tracked in these cases, we don't have to worry about any aliasing issues.

@ripytide
Copy link
Contributor

Would it not be a better idea to rather than creating a special "unsafe" cursor to simply have unlimited &mut key access, to instead just expose some function fn try_replace_key(&mut self, new_key: K) -> Result<K, InvalidKeyError> on CursorMut which would check if the new key held up to the Ord invariants with the surrounding keys when doing the replacement.

@joshtriplett
Copy link
Member

The concept of this looks great to me. Rerolling on the basis of available review time:

r? libs-api

@rustbot rustbot added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 16, 2024
@rustbot rustbot assigned dtolnay and unassigned joshtriplett Jan 16, 2024
Copy link
Member

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love this design based on gaps. Well done.

I have thoughts about some of the small details of the API that I will write up in the tracking issue and/or create my own followup PRs for consideration. Let's go ahead and land this PR though, as it is a huge step in the right direction.

@dtolnay
Copy link
Member

dtolnay commented Jan 24, 2024

@bors r+

@bors
Copy link
Contributor

bors commented Jan 24, 2024

📌 Commit d085f34 has been approved by dtolnay

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 24, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 25, 2024
Rewrite the BTreeMap cursor API using gaps

Tracking issue: rust-lang#107540

Currently, a `Cursor` points to a single element in the tree, and allows moving to the next or previous element while mutating the tree. However this was found to be confusing and hard to use.

This PR completely refactors cursors to instead point to a gap between two elements in the tree. This eliminates the need for a "ghost" element that exists after the last element and before the first one. Additionally, `upper_bound` and `lower_bound` now have a much clearer meaning.

The ability to mutate keys is also factored out into a separate `CursorMutKey` type which is unsafe to create. This makes the API easier to use since it avoids duplicated versions of each method with and without key mutation.

API summary:

```rust
impl<K, V> BTreeMap<K, V> {
    fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
}

struct Cursor<'a, K: 'a, V: 'a>;

impl<'a, K, V> Cursor<'a, K, V> {
    fn next(&mut self) -> Option<(&'a K, &'a V)>;
    fn prev(&mut self) -> Option<(&'a K, &'a V)>;
    fn peek_next(&self) -> Option<(&'a K, &'a V)>;
    fn peek_prev(&self) -> Option<(&'a K, &'a V)>;
}

struct CursorMut<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&K, &mut V)>;
    fn prev(&mut self) -> Option<(&K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct CursorMutKey<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&mut K, &mut V)>;
    fn prev(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&mut K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct UnorderedKeyError;
```
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 25, 2024
…iaskrgr

Rollup of 7 pull requests

Successful merges:

 - rust-lang#118208 (Rewrite the BTreeMap cursor API using gaps)
 - rust-lang#120099 (linker: Refactor library linking methods in `trait Linker`)
 - rust-lang#120165 (Switch `NonZero` alias direction.)
 - rust-lang#120288 (Bump `askama` version)
 - rust-lang#120306 (Clean up after clone3 removal from pidfd code (docs and tests))
 - rust-lang#120316 (Don't call `walk_` functions directly if there is an equivalent `visit_` method)
 - rust-lang#120330 (Remove coroutine info when building coroutine drop body)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Jan 25, 2024
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#118208 (Rewrite the BTreeMap cursor API using gaps)
 - rust-lang#120099 (linker: Refactor library linking methods in `trait Linker`)
 - rust-lang#120288 (Bump `askama` version)
 - rust-lang#120306 (Clean up after clone3 removal from pidfd code (docs and tests))
 - rust-lang#120316 (Don't call `walk_` functions directly if there is an equivalent `visit_` method)
 - rust-lang#120330 (Remove coroutine info when building coroutine drop body)
 - rust-lang#120332 (Remove unused struct)
 - rust-lang#120338 (Fix links to [strict|exposed] provenance sections of `[std|core]::ptr`)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 37f0232 into rust-lang:master Jan 26, 2024
11 checks passed
@rustbot rustbot added this to the 1.77.0 milestone Jan 26, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Jan 26, 2024
Rollup merge of rust-lang#118208 - Amanieu:btree_cursor2, r=dtolnay

Rewrite the BTreeMap cursor API using gaps

Tracking issue: rust-lang#107540

Currently, a `Cursor` points to a single element in the tree, and allows moving to the next or previous element while mutating the tree. However this was found to be confusing and hard to use.

This PR completely refactors cursors to instead point to a gap between two elements in the tree. This eliminates the need for a "ghost" element that exists after the last element and before the first one. Additionally, `upper_bound` and `lower_bound` now have a much clearer meaning.

The ability to mutate keys is also factored out into a separate `CursorMutKey` type which is unsafe to create. This makes the API easier to use since it avoids duplicated versions of each method with and without key mutation.

API summary:

```rust
impl<K, V> BTreeMap<K, V> {
    fn lower_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn lower_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound<Q>(&self, bound: Bound<&Q>) -> Cursor<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
    fn upper_bound_mut<Q>(&mut self, bound: Bound<&Q>) -> CursorMut<'_, K, V>
    where
        K: Borrow<Q> + Ord,
        Q: Ord;
}

struct Cursor<'a, K: 'a, V: 'a>;

impl<'a, K, V> Cursor<'a, K, V> {
    fn next(&mut self) -> Option<(&'a K, &'a V)>;
    fn prev(&mut self) -> Option<(&'a K, &'a V)>;
    fn peek_next(&self) -> Option<(&'a K, &'a V)>;
    fn peek_prev(&self) -> Option<(&'a K, &'a V)>;
}

struct CursorMut<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&K, &mut V)>;
    fn prev(&mut self) -> Option<(&K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct CursorMutKey<'a, K: 'a, V: 'a>;

impl<'a, K, V> CursorMut<'a, K, V> {
    fn next(&mut self) -> Option<(&mut K, &mut V)>;
    fn prev(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_next(&mut self) -> Option<(&mut K, &mut V)>;
    fn peek_prev(&mut self) -> Option<(&mut K, &mut V)>;

    unsafe fn insert_after_unchecked(&mut self, key: K, value: V);
    unsafe fn insert_before_unchecked(&mut self, key: K, value: V);
    fn insert_after(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn insert_before(&mut self, key: K, value: V) -> Result<(), UnorderedKeyError>;
    fn remove_next(&mut self) -> Option<(K, V)>;
    fn remove_prev(&mut self) -> Option<(K, V)>;

    fn as_cursor(&self) -> Cursor<'_, K, V>;

    unsafe fn with_mutable_key(self) -> CursorMutKey<'a, K, V, A>;
}

struct UnorderedKeyError;
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants