maplike 0.12.1

Derive macros for maplike.
Documentation

Docs Crates.io MIT OR Apache 2.0

maplike

Traits for abstract containers and operations over them.

This crate provides traits for common operations over map-like, set-like, and vec-like data structures: .get(), .set(), .modify(), .insert(), .remove(), .push(), .pop(), .put(), .clear(), .len(), .with_one(), .assign(), and .into_iter().

For bidirectional maps, there are also variants of the get and remove operations by left and right key: .get_by_left(), .get_by_right(), .remove_by_left(), .remove_by_right().

For brevity and convenience, we also provide Scalarlike, Maplike, Setlike, Arraylike, and Veclike abstract container traits that join together traits of multiple operations.

The traits are implemented for many containers from std and third-party crates. See the Supported containers section for a complete list.

Basically, this very similar to Python's collections.abc, but is in Rust, and with traits not only for different kinds of containers, but also for each operation. And every container is treated as if it was a map. If it is not really a map, it is treated as if its key type was usize, even when there can be at most only one element. Hence the crate name, maplike.

This library is maintained and champaigned (aka. dogfooded) by the author, who uses has it as a dependency for

  • undoredo, a versatile crate for implementing Undo/Redo and non-linear history tree using sparse deltas (diffs), snapshots, or commands on arbitrary data structures;
  • dcel, a crate that implements the half-edge data structure (aka. doubly connected edge list, DCEL) generically over its underlying containers.

Usage

Adding dependency

First, add maplike as a dependency to your Cargo.toml:

[dependencies]
maplike = { version = "0.12.1", features = ["derive"] }

The derive feature flag is only needed if you want to derive Assign or Container traits using derive macros: #[derive(Assign)] or [#[derive(Container)]](https://docs.rs/maplike/latest/maplike/derive.Container .html).

Usage examples

maplike's traits allow you to write functions that are generic over many different collection types. A single trait like Get is enough to abstract over vectors, arrays, and maps alike.

use std::collections::{BTreeMap, HashMap};

use maplike::ops::Get;

// Generic over any collection implementing the `Get` trait.
fn get_second_element<C: Get<usize>>(collection: &C) -> Option<&C::Value> {
    collection.get(&1)
}

// `get_second_element()` works for `Vec`s, arrays, `BTreeMap`s, `HashMap`s with
// the very same code.
assert_eq!(get_second_element(&vec![10, 20, 30]), Some(&20));
assert_eq!(get_second_element(&[10, 20, 30]), Some(&20));
assert_eq!(get_second_element(&BTreeMap::from([(0, 10), (1, 20)])), Some(&20));
assert_eq!(get_second_element(&HashMap::from([(0, 10), (1, 20)])), Some(&20));

An abstract container trait can bundle together several traits for container methods together in one short bound. For example, Veclike joins together (Get, Set, Push, Pop, Clear, Len, and Index), thus allowing code that is generic over Vec, [smallvec::SmallVec], [tinyvec::ArrayVec](https://docs.rs/tinyvec/latest/tinyvec/struct.ArrayVec.htm l), and tinyvec::TinyVec.

use maplike::containers::{Container, Veclike};
use maplike::ops::{Clear, Push};

// This function is generic over any `Veclike` collection. The `Veclike` bound
// provides `.clear()`, `.push()` and many other methods at once.
fn replace_all<C: Veclike<usize, Value = i32>>(collection: &mut C, values: &[i32]) {
    collection.clear();
    for &value in values {
        collection.push(value);
    }
}

// `replace_all()` now works for any `Veclike` collection.

// Works on `Vec`,
let mut vec = Vec::new();
replace_all(&mut vec, &[1, 2, 3]);
assert_eq!(vec, [1, 2, 3]);
replace_all(&mut vec, &[4, 5, 6]);
assert_eq!(vec, [4, 5, 6]);

#[cfg(feature = "smallvec")]
{
    use smallvec::SmallVec;

    // Works on `smallvec::SmallVec`.
    let mut small_vec: SmallVec<[i32; 8]> = SmallVec::new();
    replace_all(&mut small_vec, &[7, 8, 9]);
    assert_eq!(small_vec.as_slice(), [7, 8, 9]);
}

#[cfg(feature = "tinyvec")]
{
    use tinyvec::{ArrayVec, TinyVec};

    // Works on `tinyvec::ArrayVec`.
    let mut tiny_array_vec: ArrayVec<[i32; 8]> = ArrayVec::new();
    replace_all(&mut tiny_array_vec, &[7, 8, 9]);
    assert_eq!(tiny_array_vec.as_slice(), [7, 8, 9]);

    // Works on `tinyvec::TinyVec`.
    let mut tiny_vec: TinyVec<[i32; 8]> = TinyVec::new();
    replace_all(&mut tiny_vec, &[10, 11, 12]);
    assert_eq!(tiny_vec.as_slice(), [10, 11, 12]);
}

// NOTE: `arrayvec::ArrayVec` and `arrayvec::ArrayString` are not `Veclike`
// because they do not implement `Index`.

Supported containers

Standard library

Rust's standard library containers are supported via built-in convenience implementations:

  • HashMap, gated by the std feature (enabled by default);
  • HashSet, gated by the std feature (enabled by default);
  • BTreeMap, gated by the alloc feature (enabled by default);
  • BTreeSet, gated by the alloc feature (enabled by default);
  • Vec, gated by the alloc feature (enabled by default);
  • Box, gated by the alloc feature (enabled by default);
  • Rc, gated by the alloc feature (enabled by default);
  • Arc, gated by the std feature (enabled by default);
  • Option, not feature-gated;
  • Weak and sync::Weak, gated by the alloc and std features respectively (both enabled by default).

Primitives

All Rust's scalar types (i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64, char, bool, ()) are supported and treated like single-element, usize-keyed maps.

Rust's compound types (arrays and tuples) are supported and treated like usize-keyed maps.

maplike's types

maplike provides and supports its own generic type, One, for a collection that always has only one element. Think Option but without None or Box but without pointer indirection, behaving like a collection despite holding a value not reference, allocated on the stack.

Wrap your value in this type if you need to treat it as a single-element, usize-keyed map and your type does not happen to be a Rust primitive.

Third-party types

In addition to the standard library, maplike has built-in feature-gated trait implementations for data structures from certain external crates:

For some examples of practical use, see the examples directory of the undoredo crate.

Unsupported collections

Standard library's VecDeque is unsupported.

Among stable vector data structures, Slab, SlotMap, generational-arena are not supported because they lack interfaces for insertion at an arbitrary key.

Technical sidenotes

Unlike maps and sets, not all stable vector data structures allow insertion and removal at arbitrary indexes regardless of whether they are vacant, occupied or out of bounds. For StableVec, we managed to implement inserting at out-of-bound indexes by changing the length before insertion using the .reserve_for() method. For thunderdome::Arena, we insert at arbitrary key directly via the .insert_at() method. Collections for which we could not achieve this are documented in the section below.

For Slab, an interface to insert at an arbitrary key is missing apparently because the freelist Slab uses to keep track of its vacant indexes is only singly-linked, not doubly-linked. Inserting an element at an arbitrary vacant index would require removing that index from the freelist. But since there is no backwards link available at a given key, doing so would require traversing the freelist from the beginning to find the position of the previous node, which would incur an overly slow O(n) time cost.