multi-index-container 0.1.0

A Rust procedural macro for collections that can be efficiently looked up, mutated, and removed by multiple indexes simultaneously
Documentation
# multi_index_container

A Rust procedural macro for collections that can be efficiently looked up, mutated, and removed by multiple indexes simultaneously, with compile-time guarantees that unique constraints are upheld.

> While I have done my best to test it thoroughly, bugs may still be present. If you encounter any issues, bug reports are greatly appreciated.

## Overview

`multi_index_container` lets you define a typed map over any struct where fields can be designated as indexes. Each index can be unique or non-unique, ordered or unordered. The macro generates all lookup, mutation, and removal methods at compile time with no runtime overhead from reflection or dynamic dispatch.

## Index Types

| Index type | Duplicates | Ordered |
|---|---|---|
| `unique` | No | No |
| `non_unique` | Yes | No |
| `unique_ordered` | No | Yes |
| `non_unique_ordered` | Yes | Yes |

## Usage

```rust
use multi_index_map::multi_index_map;

#[derive(Debug, Clone, PartialEq)]
struct Person {
    email: String,
    age: u32,
    department: String,
    seniority: u32,
    team: String,
}

multi_index_map! {
    #[derive(Debug)]
    PersonMap<Person> {
        unique email: String => |p| p.email.clone(),
        non_unique age: u32 => |p| p.age,
        non_unique department: String => |p| p.department.clone(),
        unique_ordered seniority: u32 => |p| p.seniority,
        non_unique_ordered team: String => |p| p.team.clone(),
    }
}
```

This generates a `PersonMap` type with methods for every index and every combination of indexes.

### Documentation

This macro will also generate documentation for the types and methods generated which can be read by generating documenation `cargo doc --open --no-deps`.

## Inserting

```rust
let mut map = PersonMap::new();

// Returns Err if a unique constraint is violated
map.insert(Person { email: "alice@example.com".into(), age: 30, .. }).unwrap();

// Overwrites any entry that clashes on a unique index; all clashing entries are removed
map.insert_or_overwrite(Person { email: "alice@example.com".into(), age: 99, .. });

// Extend from an iterator; returns a Vec of values that failed to insert
let errors = map.extend(people);
```

## Looking Up

Single-index lookups are generated for every field:

```rust
// Unique indexes return Option<&T>
let alice = map.get_by_email(&"alice@example.com".to_string());

// Non-unique indexes return impl Iterator<Item = &T>
let engineers: Vec<_> = map.get_by_department(&"engineering".to_string()).collect();

// Ordered indexes support range queries
let seniors: Vec<_> = map.get_by_seniority_range(5..).collect();
```

Combined lookups are generated for every combination of non-unique indexes:

```rust
let results: Vec<_> = map
    .get_by_department_team(&"engineering".to_string(), &"backend".to_string())
    .collect();

let results: Vec<_> = map
    .get_by_age_department_team(&30, &"engineering".to_string(), &"backend".to_string())
    .collect();
```

Many index lookups are generated, with the documentation generated running `cargo doc --open --no-deps` on your crate.

## Mutating and Removing

`get_mut_by_*` returns a `MutEntries` handle that supports chained operations before modifications:

```rust
// Filter, then modify the first match
map.get_mut_by_department_team(&"engineering".to_string(), &"backend".to_string())
    .filter(|p| p.age > 25)
    .first()
    .unwrap()
    .modify(|p| p.seniority += 1)
    .unwrap();

// Remove a specific entry
let removed = map
    .get_mut_by_email(&"alice@example.com".to_string())
    .unwrap()
    .remove();

// Remove all matching entries, getting back the evicted values
let removed: Vec<Person> = map
    .get_mut_by_team(&"backend".to_string())
    .remove_all();
```

### modify_or_remove

`modify_or_remove` removes the entry, applies your closure, then re-inserts. If re-insertion fails due to a unique constraint clash, the entry is permanently removed and the failed value is returned in the `Err`:

```rust
let result = map
    .get_mut_by_email(&"alice@example.com".to_string())
    .unwrap()
    .modify_or_remove(|p| p.seniority = 10);

match result {
    Ok(()) => { /* updated successfully */ }
    Err(e) => { /* e.value is the orphaned Person */ }
}
```

## Unique Constraint Violations

Any insertion that would violate a `unique` or `unique_ordered` index returns an `Err` containing the value that could not be inserted. `insert_or_overwrite` instead removes all clashing entries, including across multiple indexes, and inserts the new value unconditionally.