From 4230c36accbaf37d5190e480c727adec6ea6f499 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 9 Apr 2026 12:20:25 -0700 Subject: [PATCH 1/6] Update to edition 2024, MSRV 1.85 --- .github/workflows/ci.yml | 20 +++----------------- .rustfmt.toml | 2 +- Cargo.toml | 8 ++++---- README.md | 2 +- src/lib.rs | 2 +- src/macros.rs | 4 ++-- test-nostd/Cargo.toml | 3 +-- test-serde/Cargo.toml | 3 +-- test-sval/Cargo.toml | 3 +-- tests/quick.rs | 4 ++-- 10 files changed, 17 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81290b3..fddf5f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: include: - - rust: 1.82.0 # MSRV + - rust: 1.85.0 # MSRV features: - rust: stable features: arbitrary @@ -40,12 +40,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Lock MSRV-compatible dependencies - if: matrix.rust == '1.82.0' - env: - CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback - # Note that this uses the runner's pre-installed stable cargo - run: cargo generate-lockfile - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -72,19 +66,13 @@ jobs: strategy: matrix: include: - - rust: 1.82.0 + - rust: 1.85.0 target: thumbv6m-none-eabi - rust: stable target: thumbv6m-none-eabi steps: - uses: actions/checkout@v4 - - name: Lock MSRV-compatible dependencies - if: matrix.rust == '1.82.0' - env: - CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback - # Note that this uses the runner's pre-installed stable cargo - run: cargo generate-lockfile - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -124,14 +112,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - uses: dtolnay/rust-toolchain@1.82.0 # MSRV + - uses: dtolnay/rust-toolchain@1.85.0 # MSRV - uses: taiki-e/install-action@v2 with: tool: cargo-hack - name: Lock minimal direct dependencies run: cargo +nightly hack generate-lockfile --remove-dev-deps -Z direct-minimal-versions - env: - CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback - name: Build (nightly) run: cargo +nightly build --verbose --all-features - name: Build (MSRV) diff --git a/.rustfmt.toml b/.rustfmt.toml index 3a26366..f216078 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1 +1 @@ -edition = "2021" +edition = "2024" diff --git a/Cargo.toml b/Cargo.toml index b53980a..c9d4020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "ringmap" -edition = "2021" -version = "0.2.4" +edition = "2024" +version = "0.2.5" documentation = "https://docs.rs/ringmap/" repository = "https://github.com/indexmap-rs/ringmap" license = "Apache-2.0 OR MIT" description = "A hash table with consistent deque-like order and fast iteration." keywords = ["hashmap", "no_std"] categories = ["data-structures", "no-std"] -rust-version = "1.82" +rust-version = "1.85" [lib] bench = false @@ -32,7 +32,7 @@ serde = { version = "1.0.220", default-features = false, optional = true } [dev-dependencies] itertools = "0.14" fastrand = { version = "2", default-features = false } -quickcheck = { version = "1.0", default-features = false } +quickcheck = { version = "1.1", default-features = false } fnv = "1.0" serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/README.md b/README.md index e0672d7..ebbb3f0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://github.com/indexmap-rs/ringmap/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/indexmap-rs/ringmap/actions) [![crates.io](https://img.shields.io/crates/v/ringmap.svg)](https://crates.io/crates/ringmap) [![docs](https://docs.rs/ringmap/badge.svg)](https://docs.rs/ringmap) -[![rustc](https://img.shields.io/badge/rust-1.82%2B-orange.svg)](https://img.shields.io/badge/rust-1.82%2B-orange.svg) +[![rustc](https://img.shields.io/badge/rust-1.85%2B-orange.svg)](https://img.shields.io/badge/rust-1.85%2B-orange.svg) A pure-Rust hash table which preserves (in a limited sense) insertion order, with efficient deque-like manipulation of both the front and back ends. diff --git a/src/lib.rs b/src/lib.rs index 49c3d46..1c9e36c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ //! //! ### Rust Version //! -//! This version of ringmap requires Rust 1.82 or later. +//! This version of ringmap requires Rust 1.85 or later. //! //! The ringmap 0.2 release series will use a carefully considered version //! upgrade policy, where in a later 0.x version, we will raise the minimum diff --git a/src/macros.rs b/src/macros.rs index 0b05db2..15c7732 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,7 +23,7 @@ macro_rules! ringmap_with_default { ($H:ty; $($key:expr => $value:expr,)+) => { $crate::ringmap_with_default!($H; $($key => $value),+) }; ($H:ty; $($key:expr => $value:expr),*) => {{ - let builder = ::core::hash::BuildHasherDefault::<$H>::default(); + let builder = ::core::hash::BuildHasherDefault::<$H>::new(); const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]); #[allow(unused_mut)] // Specify your custom `H` (must implement Default + Hasher) as the hasher: @@ -97,7 +97,7 @@ macro_rules! ringmap { macro_rules! ringset_with_default { ($H:ty; $($value:expr,)+) => { $crate::ringset_with_default!($H; $($value),+) }; ($H:ty; $($value:expr),*) => {{ - let builder = ::core::hash::BuildHasherDefault::<$H>::default(); + let builder = ::core::hash::BuildHasherDefault::<$H>::new(); const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]); #[allow(unused_mut)] // Specify your custom `H` (must implement Default + Hash) as the hasher: diff --git a/test-nostd/Cargo.toml b/test-nostd/Cargo.toml index df5e223..c92f807 100644 --- a/test-nostd/Cargo.toml +++ b/test-nostd/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "test-nostd" -version = "0.1.0" publish = false -edition = "2021" +edition = "2024" [dependencies.ringmap] path = ".." diff --git a/test-serde/Cargo.toml b/test-serde/Cargo.toml index 7991273..a2b593d 100644 --- a/test-serde/Cargo.toml +++ b/test-serde/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "test-serde" -version = "0.1.0" publish = false -edition = "2021" +edition = "2024" [dependencies] diff --git a/test-sval/Cargo.toml b/test-sval/Cargo.toml index b380f66..18b99a7 100644 --- a/test-sval/Cargo.toml +++ b/test-sval/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "test-sval" -version = "0.1.0" publish = false -edition = "2021" +edition = "2024" [dependencies] diff --git a/tests/quick.rs b/tests/quick.rs index a1a2e9f..96ce277 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -61,7 +61,7 @@ macro_rules! quickcheck_limit { let mut quickcheck = QuickCheck::new(); if cfg!(miri) { quickcheck = quickcheck - .gen(Gen::new(10)) + .rng(Gen::new(10)) .tests(10) .max_tests(100); } @@ -219,7 +219,7 @@ quickcheck_limit! { } } } - let hsorted = hmap.iter().sorted_by_key(|(&k, _)| (k.unsigned_abs(), k)); + let hsorted = hmap.iter().sorted_by_key(|&(&k, _)| (k.unsigned_abs(), k)); itertools::assert_equal(hsorted, &map); itertools::assert_equal(&map, &map2); true From 09e5439d1428407442422b6411f04c17b73bea76 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 9 Apr 2026 12:20:39 -0700 Subject: [PATCH 2/6] `cargo fmt` with edition 2024 --- src/inner/entry.rs | 4 ++-- src/map/serde_seq.rs | 2 +- src/map/slice.rs | 2 +- src/rayon/map.rs | 2 +- src/rayon/set.rs | 2 +- src/set.rs | 2 +- src/set/iter.rs | 2 +- test-serde/src/lib.rs | 4 ++-- test-sval/src/lib.rs | 4 ++-- tests/equivalent_trait.rs | 2 +- tests/quick.rs | 27 +++++++++++++++------------ 11 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/inner/entry.rs b/src/inner/entry.rs index 931e955..dd98119 100644 --- a/src/inner/entry.rs +++ b/src/inner/entry.rs @@ -1,6 +1,6 @@ -use super::{equivalent, get_hash, Bucket, Core, OffsetIndex}; -use crate::map::{Entry, IndexedEntry}; +use super::{Bucket, Core, OffsetIndex, equivalent, get_hash}; use crate::HashValue; +use crate::map::{Entry, IndexedEntry}; use core::cmp::Ordering; use core::mem; diff --git a/src/map/serde_seq.rs b/src/map/serde_seq.rs index 0645fa9..536aa79 100644 --- a/src/map/serde_seq.rs +++ b/src/map/serde_seq.rs @@ -25,10 +25,10 @@ use core::fmt::{self, Formatter}; use core::hash::{BuildHasher, Hash}; use core::marker::PhantomData; +use crate::RingMap; use crate::map::Slice as MapSlice; use crate::serde::cautious_capacity; use crate::set::Slice as SetSlice; -use crate::RingMap; /// Serializes a [`map::Slice`][MapSlice] as an ordered sequence. /// diff --git a/src/map/slice.rs b/src/map/slice.rs index 4ae04b1..ca8c80c 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -1,6 +1,6 @@ use super::{Bucket, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}; -use crate::util::{slice_eq, try_simplify_range}; use crate::GetDisjointMutError; +use crate::util::{slice_eq, try_simplify_range}; use alloc::boxed::Box; use alloc::collections::VecDeque; diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 805ddeb..199de9c 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -15,9 +15,9 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use core::ops::RangeBounds; -use crate::map::Slice; use crate::Bucket; use crate::RingMap; +use crate::map::Slice; impl IntoParallelIterator for RingMap where diff --git a/src/rayon/set.rs b/src/rayon/set.rs index 848f12f..bd6bce2 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -16,8 +16,8 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use core::ops::RangeBounds; -use crate::set::Slice; use crate::RingSet; +use crate::set::Slice; type Bucket = crate::Bucket; diff --git a/src/set.rs b/src/set.rs index 9967e34..d65addc 100644 --- a/src/set.rs +++ b/src/set.rs @@ -13,9 +13,9 @@ pub use self::iter::{ pub use self::mutable::MutableValues; pub use self::slice::Slice; +use crate::TryReserveError; #[cfg(feature = "rayon")] pub use crate::rayon::set as rayon; -use crate::TryReserveError; #[cfg(feature = "std")] use std::hash::RandomState; diff --git a/src/set/iter.rs b/src/set/iter.rs index aa9506f..77729d1 100644 --- a/src/set/iter.rs +++ b/src/set/iter.rs @@ -1,5 +1,5 @@ -use crate::inner::extract::ExtractCore; use crate::inner::Core; +use crate::inner::extract::ExtractCore; use super::{Bucket, RingSet}; use crate::map::Buckets; diff --git a/test-serde/src/lib.rs b/test-serde/src/lib.rs index b61c0b0..a549f56 100644 --- a/test-serde/src/lib.rs +++ b/test-serde/src/lib.rs @@ -1,9 +1,9 @@ #![cfg(test)] use fnv::FnvBuildHasher; -use ringmap::{ringmap, ringset, RingMap, RingSet}; +use ringmap::{RingMap, RingSet, ringmap, ringset}; use serde::{Deserialize, Serialize}; -use serde_test::{assert_tokens, Token}; +use serde_test::{Token, assert_tokens}; #[test] fn test_serde_map() { diff --git a/test-sval/src/lib.rs b/test-sval/src/lib.rs index 499645b..e136f91 100644 --- a/test-sval/src/lib.rs +++ b/test-sval/src/lib.rs @@ -1,8 +1,8 @@ #![cfg(test)] use fnv::FnvBuildHasher; -use ringmap::{ringmap, ringset, RingMap, RingSet}; -use sval_test::{assert_tokens, Token}; +use ringmap::{RingMap, RingSet, ringmap, ringset}; +use sval_test::{Token, assert_tokens}; #[test] fn test_sval_map() { diff --git a/tests/equivalent_trait.rs b/tests/equivalent_trait.rs index 8ab2cff..f4a834f 100644 --- a/tests/equivalent_trait.rs +++ b/tests/equivalent_trait.rs @@ -1,5 +1,5 @@ -use ringmap::ringmap; use ringmap::Equivalent; +use ringmap::ringmap; use std::hash::Hash; diff --git a/tests/quick.rs b/tests/quick.rs index 96ce277..50cb425 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -557,10 +557,11 @@ where // Check both iteration order and hash lookups assert!(map.keys().eq(vec.iter())); - assert!(vec - .iter() - .enumerate() - .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + assert!( + vec.iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) }) + ); TestResult::passed() } @@ -584,10 +585,11 @@ where // Check both iteration order and hash lookups assert!(map.keys().eq(vec.iter())); - assert!(vec - .iter() - .enumerate() - .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + assert!( + vec.iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) }) + ); TestResult::passed() } @@ -613,10 +615,11 @@ where // Check both iteration order and hash lookups assert!(map.keys().eq(vec.iter())); - assert!(vec - .iter() - .enumerate() - .all(|(i, x)| { map.get_index_of(x) == Some(i) })); + assert!( + vec.iter() + .enumerate() + .all(|(i, x)| { map.get_index_of(x) == Some(i) }) + ); TestResult::passed() } From e3ef8afb0906340e54fa3e3758783cf5e615a1eb Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 2 Apr 2026 17:48:02 -0700 Subject: [PATCH 3/6] Make `Slice::{first,last,split_*}_mut` methods `const` (cherry picked from commit 5c237a2ab7db4017b057f6b52e28c78dd427cd94) --- src/lib.rs | 6 +++--- src/map/slice.rs | 35 +++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1c9e36c..609ed21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,7 +176,7 @@ impl Bucket { const fn value_ref(&self) -> &V { &self.value } - fn value_mut(&mut self) -> &mut V { + const fn value_mut(&mut self) -> &mut V { &mut self.value } fn key(self) -> K { @@ -191,10 +191,10 @@ impl Bucket { const fn refs(&self) -> (&K, &V) { (&self.key, &self.value) } - fn ref_mut(&mut self) -> (&K, &mut V) { + const fn ref_mut(&mut self) -> (&K, &mut V) { (&self.key, &mut self.value) } - fn muts(&mut self) -> (&mut K, &mut V) { + const fn muts(&mut self) -> (&mut K, &mut V) { (&mut self.key, &mut self.value) } } diff --git a/src/map/slice.rs b/src/map/slice.rs index ca8c80c..c090170 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -26,7 +26,7 @@ impl Slice { unsafe { &*(entries as *const [Bucket] as *const Self) } } - pub(super) fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { + pub(super) const fn from_mut_slice(entries: &mut [Bucket]) -> &mut Self { unsafe { &mut *(entries as *mut [Bucket] as *mut Self) } } @@ -50,7 +50,7 @@ impl Slice { } /// Returns an empty mutable slice. - pub fn new_mut<'a>() -> &'a mut Self { + pub const fn new_mut<'a>() -> &'a mut Self { Self::from_mut_slice(&mut []) } @@ -106,8 +106,12 @@ impl Slice { } /// Get the first key-value pair, with mutable access to the value. - pub fn first_mut(&mut self) -> Option<(&K, &mut V)> { - self.entries.first_mut().map(Bucket::ref_mut) + pub const fn first_mut(&mut self) -> Option<(&K, &mut V)> { + if let [first, ..] = &mut self.entries { + Some(first.ref_mut()) + } else { + None + } } /// Get the last key-value pair. @@ -120,8 +124,12 @@ impl Slice { } /// Get the last key-value pair, with mutable access to the value. - pub fn last_mut(&mut self) -> Option<(&K, &mut V)> { - self.entries.last_mut().map(Bucket::ref_mut) + pub const fn last_mut(&mut self) -> Option<(&K, &mut V)> { + if let [.., last] = &mut self.entries { + Some(last.ref_mut()) + } else { + None + } } /// Divides one slice into two at an index. @@ -139,7 +147,7 @@ impl Slice { /// ***Panics*** if `index > len`. /// For a non-panicking alternative see [`split_at_mut_checked`][Self::split_at_mut_checked]. #[track_caller] - pub fn split_at_mut(&mut self, index: usize) -> (&mut Self, &mut Self) { + pub const fn split_at_mut(&mut self, index: usize) -> (&mut Self, &mut Self) { let (first, second) = self.entries.split_at_mut(index); (Self::from_mut_slice(first), Self::from_mut_slice(second)) } @@ -158,9 +166,12 @@ impl Slice { /// Divides one mutable slice into two at an index. /// /// Returns `None` if `index > len`. - pub fn split_at_mut_checked(&mut self, index: usize) -> Option<(&mut Self, &mut Self)> { - let (first, second) = self.entries.split_at_mut_checked(index)?; - Some((Self::from_mut_slice(first), Self::from_mut_slice(second))) + pub const fn split_at_mut_checked(&mut self, index: usize) -> Option<(&mut Self, &mut Self)> { + if let Some((first, second)) = self.entries.split_at_mut_checked(index) { + Some((Self::from_mut_slice(first), Self::from_mut_slice(second))) + } else { + None + } } /// Returns the first key-value pair and the rest of the slice, @@ -175,7 +186,7 @@ impl Slice { /// Returns the first key-value pair and the rest of the slice, /// with mutable access to the value, or `None` if it is empty. - pub fn split_first_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { + pub const fn split_first_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { if let [first, rest @ ..] = &mut self.entries { Some((first.ref_mut(), Self::from_mut_slice(rest))) } else { @@ -195,7 +206,7 @@ impl Slice { /// Returns the last key-value pair and the rest of the slice, /// with mutable access to the value, or `None` if it is empty. - pub fn split_last_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { + pub const fn split_last_mut(&mut self) -> Option<((&K, &mut V), &mut Self)> { if let [rest @ .., last] = &mut self.entries { Some((last.ref_mut(), Self::from_mut_slice(rest))) } else { From a841ebeb0eca1b2633b1575753ba3d4af16851f5 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 3 Apr 2026 11:03:08 -0700 Subject: [PATCH 4/6] Normalize the panic doc of `get_disjoint_mut` (cherry picked from commit 478fba2eb0594f0fdc70b6b93e90fae820bdf82e) --- src/map.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/map.rs b/src/map.rs index fa2efcc..61d1590 100644 --- a/src/map.rs +++ b/src/map.rs @@ -968,7 +968,9 @@ where } } - /// Return the values for `N` keys. If any key is duplicated, this function will panic. + /// Return the values for `N` keys. + /// + /// ***Panics*** if any key is duplicated. /// /// # Examples /// @@ -976,6 +978,7 @@ where /// let mut map = ringmap::RingMap::from([(1, 'a'), (3, 'b'), (2, 'c')]); /// assert_eq!(map.get_disjoint_mut([&2, &1]), [Some(&mut 'c'), Some(&mut 'a')]); /// ``` + #[track_caller] pub fn get_disjoint_mut(&mut self, keys: [&Q; N]) -> [Option<&mut V>; N] where Q: ?Sized + Hash + Equivalent, From 5400819e5b80997c68fc4dad4512e4860d4e3fc7 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 9 Apr 2026 08:42:34 -0700 Subject: [PATCH 5/6] Upgrade to `hashbrown v0.17` (cherry picked from commit 2566bec20dfbca0fa037939e67df4938c0f60f01) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c9d4020..2e0323a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bench = false [dependencies] equivalent = { version = "1.0", default-features = false } -hashbrown = { version = "0.16.1", default-features = false } +hashbrown = { version = "0.17", default-features = false } arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } From 45c4a18a23b7a6821fbd4d6acdf266269e3c6174 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 9 Apr 2026 12:25:16 -0700 Subject: [PATCH 6/6] Release 0.2.5 --- RELEASES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 5192f32..b9b6020 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,12 @@ # Releases +## 0.2.5 (2026-04-09) + +- **MSRV**: Rust 1.85.0 or later is now required. +- Updated the `hashbrown` dependency to 0.17. +- Made more `map::Slice` methods `const`: `new_mut`, `first_mut`, `last_mut`, + `split_at_mut`, `split_at_mut_checked`, `split_first_mut`, `split_last_mut` + ## 0.2.4 (2026-04-02) - Made some `Slice` methods `const`: