Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
include:
- rust: 1.63.0 # MSRV
- rust: 1.82.0 # MSRV
features:
- rust: stable
features: arbitrary
Expand All @@ -40,13 +40,8 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
if: matrix.rust == '1.63.0'
with:
path: ~/.cargo/registry/index
key: cargo-git-index
- name: Lock MSRV-compatible dependencies
if: matrix.rust == '1.63.0'
if: matrix.rust == '1.82.0'
env:
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback
# Note that this uses the runner's pre-installed stable cargo
Expand Down Expand Up @@ -77,20 +72,15 @@ jobs:
strategy:
matrix:
include:
- rust: 1.63.0
- rust: 1.82.0
target: thumbv6m-none-eabi
- rust: stable
target: thumbv6m-none-eabi

steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
if: matrix.rust == '1.63.0'
with:
path: ~/.cargo/registry/index
key: cargo-git-index
- name: Lock MSRV-compatible dependencies
if: matrix.rust == '1.63.0'
if: matrix.rust == '1.82.0'
env:
CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback
# Note that this uses the runner's pre-installed stable cargo
Expand Down Expand Up @@ -123,20 +113,18 @@ jobs:
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
if: github.event_name == 'merge_group'
- run: cargo miri nextest run
if: github.event_name == 'merge_group'
- run: cargo miri test --doc

minimal-versions:
name: Check MSRV and minimal-versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/.cargo/registry/index
key: cargo-git-index
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@1.63.0 # MSRV
- uses: dtolnay/rust-toolchain@1.82.0 # MSRV
- uses: taiki-e/install-action@v2
with:
tool: cargo-hack
Expand Down
20 changes: 14 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
[package]
name = "indexmap"
edition = "2021"
version = "2.11.4"
version = "2.12.0"
documentation = "https://docs.rs/indexmap/"
repository = "https://github.com/indexmap-rs/indexmap"
license = "Apache-2.0 OR MIT"
description = "A hash table with consistent order and fast iteration."
keywords = ["hashmap", "no_std"]
categories = ["data-structures", "no-std"]
rust-version = "1.63"
rust-version = "1.82"

[lib]
bench = false

[dependencies]
equivalent = { version = "1.0", default-features = false }
hashbrown = { version = "0.16", default-features = false }

arbitrary = { version = "1.0", optional = true, default-features = false }
quickcheck = { version = "1.0", optional = true, default-features = false }
Expand All @@ -25,10 +26,6 @@ sval = { version = "2", optional = true, default-features = false }
# deprecated: use borsh's "indexmap" feature instead.
borsh = { version = "1.2", optional = true, default-features = false }

[dependencies.hashbrown]
version = ">= 0.15.0, < 0.17.0"
default-features = false

# serde v1.0.220 is the first version that released with `serde_core`.
# This is required to avoid conflict with other `serde` users which may require an older version.
[target.'cfg(any())'.dependencies]
Expand Down Expand Up @@ -64,5 +61,16 @@ rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = ["test-nostd", "test-serde", "test-sval"]

[lints.rust]
private-bounds = "deny"
private-interfaces = "deny"
unnameable-types = "deny"
unreachable-pub = "deny"

# We *mostly* avoid unsafe code, but there are a few fine-grained cases allowed
unsafe-code = "deny"

rust-2018-idioms = "warn"

[lints.clippy]
style = "allow"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![build status](https://github.com/indexmap-rs/indexmap/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/indexmap-rs/indexmap/actions)
[![crates.io](https://img.shields.io/crates/v/indexmap.svg)](https://crates.io/crates/indexmap)
[![docs](https://docs.rs/indexmap/badge.svg)](https://docs.rs/indexmap)
[![rustc](https://img.shields.io/badge/rust-1.63%2B-orange.svg)](https://img.shields.io/badge/rust-1.63%2B-orange.svg)
[![rustc](https://img.shields.io/badge/rust-1.82%2B-orange.svg)](https://img.shields.io/badge/rust-1.82%2B-orange.svg)

A pure-Rust hash table which preserves (in a limited sense) insertion order.

Expand Down
8 changes: 8 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Releases

## 2.12.0 (2025-10-17)

- **MSRV**: Rust 1.82.0 or later is now required.
- Updated the `hashbrown` dependency to 0.16 alone.
- Error types now implement `core::error::Error`.
- Added `pop_if` methods to `IndexMap` and `IndexSet`, similar to the
method for `Vec` added in Rust 1.86.

## 2.11.4 (2025-09-18)

- Updated the `hashbrown` dependency to a range allowing 0.15 or 0.16.
Expand Down
2 changes: 1 addition & 1 deletion benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ extern crate test;
use fnv::FnvHasher;
use std::hash::BuildHasherDefault;
use std::hash::Hash;
use std::hint::black_box;
use std::sync::LazyLock;
type FnvBuilder = BuildHasherDefault<FnvHasher>;

use test::black_box;
use test::Bencher;

use indexmap::IndexMap;
Expand Down
2 changes: 2 additions & 0 deletions benches/faststring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ impl<'a, S> From<&'a S> for &'a OneShot<str>
where
S: AsRef<str>,
{
#[allow(unsafe_code)]
fn from(s: &'a S) -> Self {
let s: &str = s.as_ref();
// SAFETY: OneShot is a `repr(transparent)` wrapper
unsafe { &*(s as *const str as *const OneShot<str>) }
}
}
Expand Down
1 change: 0 additions & 1 deletion src/borsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use alloc::vec::Vec;
use core::hash::BuildHasher;
use core::hash::Hash;
use core::mem::size_of;

use borsh::error::ERROR_ZST_FORBIDDEN;
use borsh::io::{Error, ErrorKind, Read, Result, Write};
Expand Down
15 changes: 4 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// We *mostly* avoid unsafe code, but `Slice` allows it for DST casting.
#![deny(unsafe_code)]
#![warn(rust_2018_idioms)]
#![no_std]

//! [`IndexMap`] is a hash table where the iteration order of the key-value
Expand Down Expand Up @@ -60,7 +57,7 @@
//! ### Alternate Hashers
//!
//! [`IndexMap`] and [`IndexSet`] have a default hasher type
//! [`S = RandomState`][std::collections::hash_map::RandomState],
//! [`S = RandomState`][std::hash::RandomState],
//! just like the standard `HashMap` and `HashSet`, which is resistant to
//! HashDoS attacks but not the most performant. Type aliases can make it easier
//! to use alternate hashers:
Expand All @@ -79,7 +76,7 @@
//!
//! ### Rust Version
//!
//! This version of indexmap requires Rust 1.63 or later.
//! This version of indexmap requires Rust 1.82 or later.
//!
//! The indexmap 2.x release series will use a carefully considered version
//! upgrade policy, where in a later 2.x version, we will raise the minimum
Expand Down Expand Up @@ -255,9 +252,7 @@ impl core::fmt::Display for TryReserveError {
}
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for TryReserveError {}
impl core::error::Error for TryReserveError {}

// NOTE: This is copied from the slice module in the std lib.
/// The error type returned by [`get_disjoint_indices_mut`][`IndexMap::get_disjoint_indices_mut`].
Expand Down Expand Up @@ -285,6 +280,4 @@ impl core::fmt::Display for GetDisjointMutError {
}
}

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for GetDisjointMutError {}
impl core::error::Error for GetDisjointMutError {}
14 changes: 7 additions & 7 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ pub use crate::rayon::map as rayon;

use ::core::cmp::Ordering;
use ::core::fmt;
use ::core::hash::{BuildHasher, Hash, Hasher};
use ::core::hash::{BuildHasher, Hash};
use ::core::mem;
use ::core::ops::{Index, IndexMut, RangeBounds};
use alloc::boxed::Box;
use alloc::vec::Vec;

#[cfg(feature = "std")]
use std::collections::hash_map::RandomState;
use std::hash::RandomState;

pub(crate) use self::core::{ExtractCore, IndexMapCore};
use crate::util::{third, try_simplify_range};
Expand Down Expand Up @@ -813,9 +813,8 @@ where
S: BuildHasher,
{
pub(crate) fn hash<Q: ?Sized + Hash>(&self, key: &Q) -> HashValue {
let mut h = self.hash_builder.build_hasher();
key.hash(&mut h);
HashValue(h.finish() as usize)
let h = self.hash_builder.hash_one(key);
HashValue(h as usize)
}

/// Return `true` if an equivalent to `key` exists in the map.
Expand Down Expand Up @@ -1826,10 +1825,11 @@ where
// Otherwise reserve half the hint (rounded up), so the map
// will only resize twice in the worst case.
let iter = iterable.into_iter();
let (lower_len, _) = iter.size_hint();
let reserve = if self.is_empty() {
iter.size_hint().0
lower_len
} else {
(iter.size_hint().0 + 1) / 2
lower_len.div_ceil(2)
};
self.reserve(reserve);
iter.for_each(move |(k, v)| {
Expand Down
6 changes: 3 additions & 3 deletions src/map/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ struct RefMut<'a, K, V> {
}

#[inline(always)]
fn get_hash<K, V>(entries: &[Bucket<K, V>]) -> impl Fn(&usize) -> u64 + '_ {
fn get_hash<K, V>(entries: &[Bucket<K, V>]) -> impl Fn(&usize) -> u64 + use<'_, K, V> {
move |&i| entries[i].hash.get()
}

#[inline]
fn equivalent<'a, K, V, Q: ?Sized + Equivalent<K>>(
key: &'a Q,
entries: &'a [Bucket<K, V>],
) -> impl Fn(&usize) -> bool + 'a {
) -> impl Fn(&usize) -> bool + use<'a, K, V, Q> {
move |&i| Q::equivalent(key, &entries[i].key)
}

Expand Down Expand Up @@ -111,7 +111,7 @@ where

impl<K, V> IndexMapCore<K, V> {
/// The maximum capacity before the `entries` allocation would exceed `isize::MAX`.
const MAX_ENTRIES_CAPACITY: usize = (isize::MAX as usize) / mem::size_of::<Bucket<K, V>>();
const MAX_ENTRIES_CAPACITY: usize = (isize::MAX as usize) / size_of::<Bucket<K, V>>();

#[inline]
pub(crate) const fn new() -> Self {
Expand Down
Loading