indexmap-rs/indexmap

Exposing with_capacity with custom hasher?

Closed this issue · 3 comments

As far as I can tell/understand, it looks like the with_capacity function is available only when using a default std IndexMap using RandomState. However, I'm not deeply familiar with this part of Rust, but I believe it should be possible to expose a version of with_capacity with any hasher given in a core::hash::BuildHasherDefault, for example:

pub type AHashIndexMap<K, V> = indexmap::IndexMap<K, V, core::hash::BuildHasherDefault<ahash::AHasher>>;

I believe hashbrown exposes a with_capacity method in these situations. Does indexmap do something similar? If not, it would be a handy option.

In hashbrown, the default hasher is ahash, and that's the only one that supports with_capacity. Notice that its impl block is only for DefaultHashBuilder, which is an alias for BuildHasherDefault<AHasher>. In indexmap, we've chosen to stick with the standard library's default hasher, and again that's the only one that supports with_capacity.

The reasons for that not being more generic are similar to the discussion of new in #265, although unfortunately there's not such an easy alternative like IndexMap::default when you want capacity. You can either use default and then reserve, or use something like with_capacity_and_hasher(cap, AHasher::default()).

Maybe there could be a new method like default_with_capacity or with_capacity_and_default_hasher, but I would prefer to see that in the standard library HashMap first so we can match the bikeshedding. :)

For my current project I re-exported indexmap from my utility crate to get a with_capacity function like so. Would this work in indexmap itself?

pub mod indexmap {
    use core::hash::BuildHasherDefault;

    pub type IndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<ahash::AHasher>>;
    pub use indexmap::map::*;

    pub trait IndexMapExt<K, V, S: Default> {
        fn with_capacity(capacity: usize) -> Self;
    }

    impl<K, V, S: Default> IndexMapExt<K, V, S> for indexmap::IndexMap<K, V, S> {
        fn with_capacity(capacity: usize) -> Self {
            Self::with_capacity_and_hasher(capacity, S::default())
        }
    }
}

It is possible to write it generically like that, but it makes type inference difficult for users, unless they always use an alias binding a specific S like you're doing. If we allowed any S in with_capacity:

// IndexMap<K, V, S> parameters are initially ambiguous
let mut map = IndexMap::with_capacity(10);

// This will drive type inference to K = &'static str and V = i32
map.insert("foo", 42);

// So that's IndexMap<&'static str, i32, S>, but the hasher type is still unknown!