-
Notifications
You must be signed in to change notification settings - Fork 18
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
feat: add CursorMut
#25
Merged
Merged
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
1bb1fe4
feat: add `CursorMut`
olebedev 09b5ecc
add `LinkedHashMap::cursor_mut` method
olebedev 41d96a0
remove unreachable! in favor of None
olebedev 2b45428
fn signature: move_at(K) -> move_at(&K)
olebedev 6b55ef0
remove move_at fn
olebedev 80f55b3
revert upgrade of rust on ci
olebedev 57e0876
cursor_mut -> cursor_{front,back}_mut
olebedev b325d8b
remove comment
olebedev 41a3ab1
tuple -> key, value
olebedev db0b224
attach node before insert
olebedev 6bcf27c
remove unnecessary closures
olebedev a520781
handle the corner-case when inserting before itself
olebedev f4b9a7f
address nitpicks
olebedev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -503,6 +503,42 @@ where | |
} | ||
} | ||
} | ||
|
||
/// Returns the `CursorMut` over the front node. | ||
/// | ||
/// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and | ||
/// will always return `None` as its current element, regardless of any move in any | ||
/// direction. | ||
pub fn cursor_front_mut(&mut self) -> CursorMut<K, V, S> { | ||
unsafe { ensure_guard_node(&mut self.values) }; | ||
let mut c = CursorMut { | ||
cur: self.values.as_ptr(), | ||
hash_builder: &self.hash_builder, | ||
free: &mut self.free, | ||
values: &mut self.values, | ||
table: &mut self.table, | ||
}; | ||
c.move_next(); | ||
c | ||
} | ||
|
||
/// Returns the `CursorMut` over the back node. | ||
/// | ||
/// Note: The `CursorMut` is pointing to the _guard_ node in an empty `LinkedHashMap` and | ||
/// will always return `None` as its current element, regardless of any move in any | ||
/// direction. | ||
pub fn cursor_back_mut(&mut self) -> CursorMut<K, V, S> { | ||
unsafe { ensure_guard_node(&mut self.values) }; | ||
let mut c = CursorMut { | ||
cur: self.values.as_ptr(), | ||
hash_builder: &self.hash_builder, | ||
free: &mut self.free, | ||
values: &mut self.values, | ||
table: &mut self.table, | ||
}; | ||
c.move_prev(); | ||
c | ||
} | ||
} | ||
|
||
impl<K, V, S> LinkedHashMap<K, V, S> | ||
|
@@ -680,7 +716,7 @@ where | |
} | ||
|
||
pub enum Entry<'a, K, V, S> { | ||
Occupied(OccupiedEntry<'a, K, V>), | ||
Occupied(OccupiedEntry<'a, K, V, S>), | ||
Vacant(VacantEntry<'a, K, V, S>), | ||
} | ||
|
||
|
@@ -755,12 +791,12 @@ impl<'a, K, V, S> Entry<'a, K, V, S> { | |
} | ||
} | ||
|
||
pub struct OccupiedEntry<'a, K, V> { | ||
pub struct OccupiedEntry<'a, K, V, S> { | ||
key: K, | ||
raw_entry: RawOccupiedEntryMut<'a, K, V>, | ||
raw_entry: RawOccupiedEntryMut<'a, K, V, S>, | ||
} | ||
|
||
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for OccupiedEntry<'_, K, V> { | ||
impl<K: fmt::Debug, V: fmt::Debug, S> fmt::Debug for OccupiedEntry<'_, K, V, S> { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("OccupiedEntry") | ||
|
@@ -770,7 +806,7 @@ impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for OccupiedEntry<'_, K, V> { | |
} | ||
} | ||
|
||
impl<'a, K, V> OccupiedEntry<'a, K, V> { | ||
impl<'a, K, V, S> OccupiedEntry<'a, K, V, S> { | ||
#[inline] | ||
pub fn key(&self) -> &K { | ||
self.raw_entry.key() | ||
|
@@ -829,6 +865,16 @@ impl<'a, K, V> OccupiedEntry<'a, K, V> { | |
self.replace_entry(value) | ||
} | ||
|
||
/// Returns a `CursorMut` over the current entry. | ||
#[inline] | ||
pub fn cursor_mut(self) -> CursorMut<'a, K, V, S> | ||
where | ||
K: Eq + Hash, | ||
S: BuildHasher, | ||
{ | ||
self.raw_entry.cursor_mut() | ||
} | ||
|
||
/// Replaces the entry's key with the key provided to `LinkedHashMap::entry`, and replaces the | ||
/// entry's value with the given `value` parameter. | ||
/// | ||
|
@@ -984,6 +1030,7 @@ where | |
|
||
match entry { | ||
Ok(occupied) => RawEntryMut::Occupied(RawOccupiedEntryMut { | ||
hash_builder: &self.map.hash_builder, | ||
free: &mut self.map.free, | ||
values: &mut self.map.values, | ||
entry: occupied, | ||
|
@@ -1015,7 +1062,7 @@ where | |
} | ||
|
||
pub enum RawEntryMut<'a, K, V, S> { | ||
Occupied(RawOccupiedEntryMut<'a, K, V>), | ||
Occupied(RawOccupiedEntryMut<'a, K, V, S>), | ||
Vacant(RawVacantEntryMut<'a, K, V, S>), | ||
} | ||
|
||
|
@@ -1076,13 +1123,14 @@ impl<'a, K, V, S> RawEntryMut<'a, K, V, S> { | |
} | ||
} | ||
|
||
pub struct RawOccupiedEntryMut<'a, K, V> { | ||
pub struct RawOccupiedEntryMut<'a, K, V, S> { | ||
hash_builder: &'a S, | ||
free: &'a mut Option<NonNull<Node<K, V>>>, | ||
values: &'a mut Option<NonNull<Node<K, V>>>, | ||
entry: hash_table::OccupiedEntry<'a, NonNull<Node<K, V>>>, | ||
} | ||
|
||
impl<'a, K, V> RawOccupiedEntryMut<'a, K, V> { | ||
impl<'a, K, V, S> RawOccupiedEntryMut<'a, K, V, S> { | ||
#[inline] | ||
pub fn key(&self) -> &K { | ||
self.get_key_value().0 | ||
|
@@ -1184,6 +1232,22 @@ impl<'a, K, V> RawOccupiedEntryMut<'a, K, V> { | |
let node = self.entry.remove().0; | ||
unsafe { remove_node(self.free, node) } | ||
} | ||
|
||
/// Returns a `CursorMut` over the current entry. | ||
#[inline] | ||
pub fn cursor_mut(self) -> CursorMut<'a, K, V, S> | ||
where | ||
K: Eq + Hash, | ||
S: BuildHasher, | ||
{ | ||
CursorMut { | ||
cur: self.entry.get().as_ptr(), | ||
hash_builder: self.hash_builder, | ||
free: self.free, | ||
values: self.values, | ||
table: self.entry.into_table(), | ||
} | ||
} | ||
} | ||
|
||
pub struct RawVacantEntryMut<'a, K, V, S> { | ||
|
@@ -1260,7 +1324,7 @@ impl<K: fmt::Debug, V: fmt::Debug, S> fmt::Debug for RawEntryMut<'_, K, V, S> { | |
} | ||
} | ||
|
||
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for RawOccupiedEntryMut<'_, K, V> { | ||
impl<K: fmt::Debug, V: fmt::Debug, S> fmt::Debug for RawOccupiedEntryMut<'_, K, V, S> { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("RawOccupiedEntryMut") | ||
|
@@ -1284,17 +1348,19 @@ impl<K, V, S> fmt::Debug for RawEntryBuilder<'_, K, V, S> { | |
} | ||
} | ||
|
||
unsafe impl<'a, K, V> Send for RawOccupiedEntryMut<'a, K, V> | ||
unsafe impl<'a, K, V, S> Send for RawOccupiedEntryMut<'a, K, V, S> | ||
where | ||
K: Send, | ||
V: Send, | ||
S: Send, | ||
{ | ||
} | ||
|
||
unsafe impl<'a, K, V> Sync for RawOccupiedEntryMut<'a, K, V> | ||
unsafe impl<'a, K, V, S> Sync for RawOccupiedEntryMut<'a, K, V, S> | ||
where | ||
K: Sync, | ||
V: Sync, | ||
S: Sync, | ||
{ | ||
} | ||
|
||
|
@@ -1344,6 +1410,28 @@ pub struct Drain<'a, K, V> { | |
marker: PhantomData<(K, V, &'a LinkedHashMap<K, V>)>, | ||
} | ||
|
||
/// The `CursorMut` struct and its implementation provide the basic mutable Cursor API for Linked | ||
/// lists as proposed in | ||
/// [here](https://github.com/rust-lang/rfcs/blob/master/text/2570-linked-list-cursors.md), with | ||
/// several exceptions: | ||
/// | ||
/// - It behaves similarly to Rust's Iterators, returning `None` when the end of the list is | ||
/// reached. A _guard_ node is positioned between the head and tail of the linked list to | ||
/// facilitate this. If the cursor is over this guard node, `None` is returned, signaling the end | ||
/// or start of the list. From this position, the cursor can move in either direction as the | ||
/// linked list is circular, with the guard node connecting the two ends. | ||
/// - The current implementation does not include an `index` method, as it does not track the index | ||
/// of its elements. It operates by providing elements as key-value tuples, allowing the value to be | ||
/// modified via a mutable reference while the key could not be changed. | ||
/// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Grammar fix: "could not" -> "cannot" Or, just something a little simpler: "It provides access to each map entry as a tuple of |
||
pub struct CursorMut<'a, K, V, S> { | ||
cur: *mut Node<K, V>, | ||
hash_builder: &'a S, | ||
free: &'a mut Option<NonNull<Node<K, V>>>, | ||
values: &'a mut Option<NonNull<Node<K, V>>>, | ||
table: &'a mut hashbrown::HashTable<NonNull<Node<K, V>>>, | ||
} | ||
|
||
impl<K, V> IterMut<'_, K, V> { | ||
#[inline] | ||
pub(crate) fn iter(&self) -> Iter<'_, K, V> { | ||
|
@@ -1677,6 +1765,147 @@ impl<'a, K, V> Drop for Drain<'a, K, V> { | |
} | ||
} | ||
|
||
impl<'a, K, V, S> CursorMut<'a, K, V, S> { | ||
/// Returns an `Option` of the current element in the list, provided it is not the | ||
/// _guard_ node, and `None` overwise. | ||
#[inline] | ||
pub fn current(&mut self) -> Option<(&K, &mut V)> { | ||
unsafe { | ||
let at = NonNull::new_unchecked(self.cur); | ||
self.peek(at) | ||
} | ||
} | ||
|
||
/// Retrieves the next element in the list (moving towards the end). | ||
#[inline] | ||
pub fn peek_next(&mut self) -> Option<(&K, &mut V)> { | ||
unsafe { | ||
let at = (*self.cur).links.value.next; | ||
self.peek(at) | ||
} | ||
} | ||
|
||
/// Retrieves the previous element in the list (moving towards the front). | ||
#[inline] | ||
pub fn peek_prev(&mut self) -> Option<(&K, &mut V)> { | ||
unsafe { | ||
let at = (*self.cur).links.value.prev; | ||
self.peek(at) | ||
} | ||
} | ||
|
||
// Retrieves the element without advancing current position to it. | ||
#[inline] | ||
fn peek(&mut self, at: NonNull<Node<K, V>>) -> Option<(&K, &mut V)> { | ||
if let Some(values) = self.values { | ||
unsafe { | ||
let node = at.as_ptr(); | ||
if node == values.as_ptr() { | ||
None | ||
} else { | ||
let entry = (*node).entry_mut(); | ||
Some((&entry.0, &mut entry.1)) | ||
} | ||
} | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
/// Updates the pointer to the current element to the next element in the | ||
/// list (that is, moving towards the end). | ||
#[inline] | ||
pub fn move_next(&mut self) { | ||
let at = unsafe { (*self.cur).links.value.next }; | ||
self.muv(at); | ||
} | ||
|
||
/// Updates the pointer to the current element to the previous element in the | ||
/// list (that is, moving towards the front). | ||
#[inline] | ||
pub fn move_prev(&mut self) { | ||
let at = unsafe { (*self.cur).links.value.prev }; | ||
self.muv(at); | ||
} | ||
|
||
// Updates the pointer to the current element to the one returned by the at closure function. | ||
#[inline] | ||
fn muv(&mut self, at: NonNull<Node<K, V>>) { | ||
self.cur = at.as_ptr(); | ||
} | ||
|
||
/// Inserts the provided key and value before the current element. It checks if an entry | ||
/// with the given key exists and, if so, replaces its value with the provided `key` | ||
/// parameter. The key is not updated; this matters for types that can be `==` without | ||
/// being identical. | ||
/// | ||
/// If the entry doesn't exist, it creates a new one. If a value has been updated, the | ||
/// function returns the *old* value wrapped with `Some` and `None` otherwise. | ||
#[inline] | ||
pub fn insert_before(&mut self, key: K, value: V) -> Option<V> | ||
where | ||
K: Eq + Hash, | ||
S: BuildHasher, | ||
{ | ||
let before = unsafe { NonNull::new_unchecked(self.cur) }; | ||
self.insert(key, value, before) | ||
} | ||
|
||
/// Inserts the provided key and value after the current element. It checks if an entry | ||
/// with the given key exists and, if so, replaces its value with the provided `key` | ||
/// parameter. The key is not updated; this matters for types that can be `==` without | ||
/// being identical. | ||
/// | ||
/// If the entry doesn't exist, it creates a new one. If a value has been updated, the | ||
/// function returns the *old* value wrapped with `Some` and `None` otherwise. | ||
#[inline] | ||
pub fn insert_after(&mut self, key: K, value: V) -> Option<V> | ||
where | ||
K: Eq + Hash, | ||
S: BuildHasher, | ||
{ | ||
let before = unsafe { (*self.cur).links.value.next }; | ||
self.insert(key, value, before) | ||
} | ||
|
||
// Inserts an element immediately before the given `before` node. | ||
#[inline] | ||
fn insert(&mut self, key: K, value: V, before: NonNull<Node<K, V>>) -> Option<V> | ||
where | ||
K: Eq + Hash, | ||
S: BuildHasher, | ||
{ | ||
unsafe { | ||
let hash = hash_key(self.hash_builder, &key); | ||
let i_entry = self | ||
.table | ||
.find_entry(hash, |o| (*o).as_ref().key_ref().eq(&key)); | ||
|
||
match i_entry { | ||
Ok(occupied) => { | ||
let mut node = *occupied.into_mut(); | ||
let pv = mem::replace(&mut node.as_mut().entry_mut().1, value); | ||
if node != before { | ||
detach_node(node); | ||
attach_before(node, before); | ||
} | ||
Some(pv) | ||
} | ||
Err(_) => { | ||
let mut new_node = allocate_node(self.free); | ||
new_node.as_mut().put_entry((key, value)); | ||
attach_before(new_node, before); | ||
let hash_builder = self.hash_builder; | ||
self.table.insert_unique(hash, new_node, move |k| { | ||
hash_key(hash_builder, (*k).as_ref().key_ref()) | ||
}); | ||
None | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
pub struct Keys<'a, K, V> { | ||
inner: Iter<'a, K, V>, | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still have a
cursor_mut
method that's private that returns the guard node, to save duplicated code.