Skip to content

feat: HashTable::try_insert_unique_within_capacity #621

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

morrisonlevi
Copy link
Contributor

@morrisonlevi morrisonlevi commented May 4, 2025

This implements #618 with this signature:

    pub fn try_insert_unique_within_capacity(
        &mut self,
        hash: u64,
        value: T,
    ) -> Result<OccupiedEntry<'_, T, A>, T>

This is safe-version from issue feedback, rather than the originally proposed unsafe version.

My personal motivation is to avoid panics. With HashTable::insert_unique, it calls RawTable::insert which calls self.reserve(1, hasher), which will panic out of necessity if allocation fails.

Today, you can call HashTable::try_reserve before calling HashTable::insert_unique to avoid the panic in practice. However, this is not compatible with techniques like using no-panic to detect at compile/link time that a function does not panic. I have verified with this patch that it's no_panic::no_panic compatible. This is not public yet, so I can't share the source for you to verify it yourself, but it's nothing too interesting.

This also has an impact on code size. I don't have concrete, trustworthy numbers for this. The compiler does not optimize away the memory growth path in insert_unique after a try_reserve, so it's easy to see that it should get smaller, it's just a matter of how much.

/// This does not check if the given element already exists in the table.
#[cfg_attr(feature = "inline-more", inline)]
pub fn insert(&mut self, hash: u64, value: T, hasher: impl Fn(&T) -> u64) -> Bucket<T> {
#[inline(always)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used #[inline(always)] unconditionally because this is a helper method which was previously written in line, so I didn't want to worry about abstraction overhead.

#[cfg_attr(feature = "inline-more", inline)]
pub fn insert(&mut self, hash: u64, value: T, hasher: impl Fn(&T) -> u64) -> Bucket<T> {
let slot = match self.find_insert_slot(hash) {
None => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose someone should check if the unlikely from the new helper find_insert_slot carries over here.

src/raw/mod.rs Outdated
///
/// This does not check if the given element already exists in the table.
#[inline]
pub(crate) fn insert_within_capacity(&mut self, hash: u64, value: T) -> Option<Bucket<T>> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used pub(crate) because I wasn't sure if we wanted to grow the RawTable API surface at all.

@morrisonlevi morrisonlevi force-pushed the safe-insert-within-capacity branch from b4184d0 to 639a216 Compare May 4, 2025 21:02
@morrisonlevi morrisonlevi force-pushed the safe-insert-within-capacity branch from 639a216 to 524982c Compare May 5, 2025 00:58
@morrisonlevi morrisonlevi force-pushed the safe-insert-within-capacity branch from 2ce56de to 4188129 Compare May 5, 2025 14:43
@morrisonlevi morrisonlevi changed the title feat: HashTable::insert_unique_within_capacity feat: HashTable::try_insert_unique_within_capacity May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant