Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
<!-- next-header -->
## Unreleased - ReleaseDate

### Added

- `Index` implementations for `IdHashMap` and `IdOrdMap`, so an item can be looked up with `map[&key]` the way `std`'s `HashMap` and `BTreeMap` allow. Like those, indexing panics if the key is absent; use `get` for a non-panicking lookup.

## [0.4.6] - 2026-07-21

### Added
Expand Down
54 changes: 54 additions & 0 deletions crates/iddqd/src/id_hash_map/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
use core::{
fmt,
hash::{BuildHasher, Hash},
ops::Index,
};
use equivalent::Equivalent;

Expand Down Expand Up @@ -1689,6 +1690,59 @@ impl<T: IdHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Eq
{
}

/// Look up an item by its key.
///
/// The `for<'k>` bound is required because the index operation borrows keys
/// from items for an unnamed lifetime, so the query type has to compare equal
/// to keys of any lifetime. That holds for the usual cases like `&str` keys
/// queried with `str`.
///
/// # Panics
///
/// Panics if no item with the given key is present. Use [`IdHashMap::get`] for
/// a non-panicking lookup.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "default-hasher")] {
/// use iddqd::{IdHashItem, IdHashMap, id_upcast};
///
/// #[derive(Debug, PartialEq, Eq, Hash)]
/// struct Item {
/// id: String,
/// value: u32,
/// }
///
/// impl IdHashItem for Item {
/// type Key<'a> = &'a str;
/// fn key(&self) -> Self::Key<'_> {
/// &self.id
/// }
/// id_upcast!();
/// }
///
/// let mut map = IdHashMap::new();
/// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap();
///
/// assert_eq!(map["foo"].value, 42);
/// # }
/// ```
impl<T, Q, S, A> Index<&Q> for IdHashMap<T, S, A>
where
T: IdHashItem,
Q: ?Sized + Hash + for<'k> Equivalent<T::Key<'k>>,
S: Clone + BuildHasher,
A: Allocator,
{
type Output = T;

#[inline]
fn index(&self, key: &Q) -> &T {
self.get(key).expect("no entry found for key")
}
}

/// The `Extend` implementation overwrites duplicates. In the future, there will
/// also be an `extend_unique` method that will return an error.
///
Expand Down
50 changes: 50 additions & 0 deletions crates/iddqd/src/id_ord_map/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
use core::{
fmt,
hash::{BuildHasher, Hash},
ops::Index,
};
use equivalent::{Comparable, Equivalent};

Expand Down Expand Up @@ -1738,3 +1739,52 @@ impl<T: IdOrdItem> FromIterator<T> for IdOrdMap<T> {
map
}
}

/// Look up an item by its key.
///
/// The `for<'k>` bound is required because the index operation borrows keys
/// from items for an unnamed lifetime, so the query type has to compare against
/// keys of any lifetime. That holds for the usual cases like `&str` keys
/// queried with `str`.
///
/// # Panics
///
/// Panics if no item with the given key is present. Use [`IdOrdMap::get`] for a
/// non-panicking lookup.
///
/// # Examples
///
/// ```
/// use iddqd::{IdOrdItem, IdOrdMap, id_upcast};
///
/// #[derive(Debug)]
/// struct Item {
/// id: String,
/// value: u32,
/// }
///
/// impl IdOrdItem for Item {
/// type Key<'a> = &'a str;
/// fn key(&self) -> Self::Key<'_> {
/// &self.id
/// }
/// id_upcast!();
/// }
///
/// let mut map = IdOrdMap::new();
/// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap();
///
/// assert_eq!(map["foo"].value, 42);
/// ```
impl<T, Q> Index<&Q> for IdOrdMap<T>
where
T: IdOrdItem,
Q: ?Sized + for<'k> Comparable<T::Key<'k>>,
{
type Output = T;

#[inline]
fn index(&self, key: &Q) -> &T {
self.get(key).expect("no entry found for key")
}
}
32 changes: 32 additions & 0 deletions crates/iddqd/tests/integration/id_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ impl IdHashItem for SimpleItem {
id_upcast!();
}

#[test]
fn index_by_key() {
let mut map = IdHashMap::<SimpleItem, HashBuilder, Alloc>::make_new();
map.insert_unique(SimpleItem { key: 1 }).unwrap();
map.insert_unique(SimpleItem { key: 20 }).unwrap();

assert_eq!(map[&1].key, 1);
assert_eq!(map[&20].key, 20);
}

#[test]
fn index_borrowed_key() {
let mut map = IdHashMap::<BorrowedItem, HashBuilder, Alloc>::make_new();
map.insert_unique(BorrowedItem {
key1: "foo",
key2: Cow::Borrowed(b"foo"),
key3: Path::new("foo"),
})
.unwrap();

// The query type `str` is shorter-lived than the stored `&'static str`
// keys, which exercises the `for<'k>` bound on the `Index` impl.
assert_eq!(map["foo"].key1, "foo");
}

#[test]
#[should_panic(expected = "no entry found for key")]
fn index_missing_key_panics() {
let map = IdHashMap::<SimpleItem, HashBuilder, Alloc>::make_new();
let _ = &map[&1];
}

#[test]
fn debug_impls() {
let mut map = IdHashMap::<SimpleItem, HashBuilder, Alloc>::make_new();
Expand Down
28 changes: 28 additions & 0 deletions crates/iddqd/tests/integration/id_ord_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,34 @@ impl IdOrdItem for SimpleItem {
id_upcast!();
}

#[test]
fn index_by_key() {
let mut map = IdOrdMap::<SimpleItem>::make_new();
map.insert_unique(SimpleItem { key: 1 }).unwrap();
map.insert_unique(SimpleItem { key: 20 }).unwrap();

assert_eq!(map[&1].key, 1);
assert_eq!(map[&20].key, 20);
}

#[test]
fn index_borrowed_key() {
let map = id_ord_map! {
BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo") },
};

// The query type `str` is shorter-lived than the stored `&'static str`
// keys, which exercises the `for<'k>` bound on the `Index` impl.
assert_eq!(map["foo"].key1, "foo");
}

#[test]
#[should_panic(expected = "no entry found for key")]
fn index_missing_key_panics() {
let map = IdOrdMap::<SimpleItem>::make_new();
let _ = &map[&1];
}

#[test]
fn debug_impls() {
let mut map = IdOrdMap::<SimpleItem>::make_new();
Expand Down
Loading