iddqd 0.3.1

Maps where keys borrow from values, including bijective and trijective maps.
Documentation
<!-- cargo-sync-rdme title [[ -->
# iddqd
<!-- cargo-sync-rdme ]] -->
<!-- cargo-sync-rdme badge [[ -->
![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/iddqd.svg?)
[![crates.io](https://img.shields.io/crates/v/iddqd.svg?logo=rust)](https://crates.io/crates/iddqd)
[![docs.rs](https://img.shields.io/docsrs/iddqd.svg?logo=docs.rs)](https://docs.rs/iddqd)
[![Rust: ^1.81.0](https://img.shields.io/badge/rust-^1.81.0-93450a.svg?logo=rust)](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
<!-- cargo-sync-rdme ]] -->
<!-- cargo-sync-rdme rustdoc [[ -->
Maps where keys are borrowed from values.

This crate consists of several map types, collectively called **ID maps**:

* [`IdOrdMap`]https://docs.rs/iddqd/0.3.1/iddqd/id_ord_map/imp/struct.IdOrdMap.html: A B-Tree based map where keys are borrowed from values.
* [`IdHashMap`]https://docs.rs/iddqd/0.3.1/iddqd/id_hash_map/imp/struct.IdHashMap.html: A hash map where keys are borrowed from values.
* [`BiHashMap`]https://docs.rs/iddqd/0.3.1/iddqd/bi_hash_map/imp/struct.BiHashMap.html: A bijective (1:1) hash map with two keys, borrowed from values.
* [`TriHashMap`]https://docs.rs/iddqd/0.3.1/iddqd/tri_hash_map/imp/struct.TriHashMap.html: A trijective (1:1:1) hash map with three keys, borrowed from values.

## Usage

* Pick your ID map type.
* Depending on the ID map type, implement [`IdOrdItem`]https://docs.rs/iddqd/0.3.1/iddqd/id_ord_map/trait_defs/trait.IdOrdItem.html, [`IdHashItem`]https://docs.rs/iddqd/0.3.1/iddqd/id_hash_map/trait_defs/trait.IdHashItem.html, [`BiHashItem`]https://docs.rs/iddqd/0.3.1/iddqd/bi_hash_map/trait_defs/trait.BiHashItem.html, or
  [`TriHashItem`]https://docs.rs/iddqd/0.3.1/iddqd/tri_hash_map/trait_defs/trait.TriHashItem.html for your value type.
* Store values in the ID map type.

### Features

This crate was built out a practical need for map types, and addresses
issues encountered using Rust’s default map types in practice at Oxide.

* Keys are retrieved from values, not stored separately from them. Separate
  storage has been a recurring pain point in our codebases: if keys are
  duplicated within values, it’s proven to be hard to maintain consistency
  between keys and values. This crate addresses that need.
* Keys may be borrowed from values, which allows for more flexible
  implementations. (They don’t have to be borrowed, but they can be.)
* There’s no `insert` method; insertion must be through either
  `insert_overwrite` or `insert_unique`. You must pick an insertion
  behavior.
* The serde implementations reject duplicate keys.

We’ve also sometimes needed to index a set of data by more than one key, or
perhaps map one key to another. For that purpose, this crate provides
[`BiHashMap`](https://docs.rs/iddqd/0.3.1/iddqd/bi_hash_map/imp/struct.BiHashMap.html) and [`TriHashMap`](https://docs.rs/iddqd/0.3.1/iddqd/tri_hash_map/imp/struct.TriHashMap.html).

* [`BiHashMap`]https://docs.rs/iddqd/0.3.1/iddqd/bi_hash_map/imp/struct.BiHashMap.html has two keys, and provides a bijection (1:1 relationship)
  between the keys.
* [`TriHashMap`]https://docs.rs/iddqd/0.3.1/iddqd/tri_hash_map/imp/struct.TriHashMap.html has three keys, and provides a trijection (1:1:1 relationship)
  between the keys.

As a consequence of the general API structure, maps can have arbitrary
non-key data associated with them as well.

### Examples

An example for [`IdOrdMap`](https://docs.rs/iddqd/0.3.1/iddqd/id_ord_map/imp/struct.IdOrdMap.html):

````rust
use iddqd::{IdOrdMap, IdOrdItem, id_upcast};

#[derive(Debug)]
struct User {
    name: String,
    age: u8,
}

// Implement IdOrdItem so the map knows how to get the key from the value.
impl IdOrdItem for User {
    // The key type can borrow from the value.
    type Key<'a> = &'a str;

    fn key(&self) -> Self::Key<'_> {
        &self.name
    }

    id_upcast!();
}

let mut users = IdOrdMap::<User>::new();

// You must pick an insertion behavior. insert_unique returns an error if
// the key already exists.
users.insert_unique(User { name: "Alice".to_string(), age: 30 }).unwrap();
users.insert_unique(User { name: "Bob".to_string(), age: 35 }).unwrap();

// Lookup by name:
assert_eq!(users.get("Alice").unwrap().age, 30);
assert_eq!(users.get("Bob").unwrap().age, 35);

// Iterate over users:
for user in &users {
    println!("User {}: {}", user.name, user.age);
}
````

An example for [`IdHashMap`](https://docs.rs/iddqd/0.3.1/iddqd/id_hash_map/imp/struct.IdHashMap.html), showing complex borrowed keys.

````rust
use iddqd::{IdHashMap, IdHashItem, id_upcast};

#[derive(Debug)]
struct Artifact {
    name: String,
    version: String,
    data: Vec<u8>,
}

// The key type is a borrowed form of the name and version. It needs to
// implement `Hash + Eq`.
#[derive(Hash, PartialEq, Eq)]
struct ArtifactKey<'a> {
    name: &'a str,
    version: &'a str,
}

impl IdHashItem for Artifact {
    // The key type can borrow from the value.
    type Key<'a> = ArtifactKey<'a>;

    fn key(&self) -> Self::Key<'_> {
        ArtifactKey {
            name: &self.name,
            version: &self.version,
        }
    }

    id_upcast!();
}

let mut artifacts = IdHashMap::<Artifact>::new();

// Add artifacts to the map.
artifacts.insert_unique(Artifact {
    name: "artifact1".to_owned(),
    version: "1.0".to_owned(),
    data: b"data1".to_vec(),
})
.unwrap();
artifacts.insert_unique(Artifact {
    name: "artifact2".to_owned(),
    version: "1.0".to_owned(),
    data: b"data2".to_vec(),
})
.unwrap();

// Look up artifacts by name and version.
assert_eq!(
    artifacts
        .get(&ArtifactKey { name: "artifact1", version: "1.0" })
        .unwrap()
        .data,
    b"data1",
);
````

## Testing

This crate is validated through a combination of:

* Unit tests
* Property-based tests using a naive map as an oracle
* Chaos tests for several kinds of buggy `Eq` and `Ord` implementations
* Miri tests for unsafe code

If you see a gap in testing, new tests are welcome. Thank you!

## No-std compatibility

Most of this crate is no-std compatible, though [`alloc`](https://doc.rust-lang.org/nightly/alloc/index.html) is required.

The [`IdOrdMap`](https://docs.rs/iddqd/0.3.1/iddqd/id_ord_map/imp/struct.IdOrdMap.html) type is not currently no-std compatible due to its use of a
thread-local. This thread-local is just a way to work around a limitation in
std’s `BTreeMap` API, though. Either a custom B-Tree implementation, or a
platform-specific notion of thread locals, would suffice to make
[`IdOrdMap`](https://docs.rs/iddqd/0.3.1/iddqd/id_ord_map/imp/struct.IdOrdMap.html) no-std compatible.

## Optional features

* `serde`: Enables serde support for all ID map types. *Not enabled by default.*
* `daft`: Enables [`daft`]https://docs.rs/daft/0.1.3/daft/index.html support for all ID map types. *Not enabled by default.*
* `std`: Enables std support. *Enabled by default.*

## Related work

* [`bimap`]https://docs.rs/bimap provides a bijective map, but does not
  have a way to associate arbitrary values with each pair of keys. However, it
  does support an ordered map type without the need for std.
* [`multi_index_map`]https://crates.io/crates/multi_index_map provides maps
  with arbitrary indexes on fields, and is more flexible than this crate. However,
  it doesn’t expose generic traits for map types, and it requires key types to be
  `Clone`. In `iddqd`, we pick a somewhat different point in the design space, but
  we think `multi_index_map` is also great.

## Minimum supported Rust version (MSRV)

This crate’s MSRV is **Rust 1.81**. In general we aim for 6 months of Rust
compatibility.

## What does iddqd mean?

The name `iddqd` is a reference to [a cheat
code](https://doomwiki.org/wiki/Doom_cheat_codes) in the classic video game
*Doom*. It has `id` in the name, and is short and memorable.
<!-- cargo-sync-rdme ]] -->

## License

This project is available under the terms of either the [Apache 2.0 license](LICENSE-APACHE) or the [MIT
license](LICENSE-MIT).

Portions adapted from [The Rust Programming Language](https://github.com/rust-lang/rust) and used under the MIT and Apache 2.0 licenses. The Rust Programming Language is (c) The Rust Project Contributors.