imbl

repository·main·Indexed 41 days ago

https://github.com/jneem/imbl

A high-performance Rust library providing immutable collection data types, serving as a maintained fork of the im crate. It features optimized implementations of HashMap, HashSet, OrdMap, OrdSet, and Vector, with support for triomphe::Arc for shared pointers. Version 7.0.2 requires Rust 1.85 or later.

Tokens
13.3K
Snippets
50
Records
70
Agent score
51%

What's inside imbl

  1. Overview of imbl immutable collections

    main

    imbl provides high-performance, immutable collection data types for Rust. It is a fork of the im crate, optimized for speed and reliability.

    Key improvements over the original im crate include:

    • Bug fixes for OrdMap, OrdSet, and Vector.
    • Significant performance improvements for HashMap, HashSet, OrdMap, and OrdSet.
    • Support for using triomphe::Arc as the underlying shared pointer implementation for better performance/flexibility.
  2. Check minimum supported Rust version for imbl

    main
    To use imbl, your project must support Rust 1.85 or later. Note that the minimum supported rust version (MSRV) may increase in the future as the crate leverages more advanced const generics.
  3. Overview of imbl immutable data structures

    main

    imbl provides efficient immutable data structures for Rust that utilize structural sharing. This means that when you copy or modify a data structure, most of the memory is shared between the original and the new version, making operations like cloning very cheap (essentially just incrementing a reference counter).

    Supported Data Structures

    TypeAlgorithmKey ConstraintsOrderUse Case
    Vector<A>RRB treeCloneinsertionGeneral purpose sequences
    HashMap<K, V>HAMTClone + Hash + EqundefinedKey-value mappings
    OrdMap<K, V>B+treeClone + OrdsortedOrdered key-value mappings
    HashSet<A>HAMTClone + Hash + EqundefinedUnique collections
    OrdSet<A>B+treeClone + OrdsortedOrdered unique collections

    Key Concepts

    • Lazy Cloning: Cloning a data structure is an instant operation that only increments a reference count. Actual data cloning happens lazily only when a specific node is modified (Copy-on-Write).
    • In-place Mutation: If you are the sole owner of a data structure (reference count is 1), imbl can perform in-place mutations, making it nearly as fast as standard mutable collections.
    • Value Requirements: Values stored in these structures must implement Clone. For expensive-to-clone values, wrap them in std::rc::Rc or std::sync::Arc to store only the pointer.
  4. Use the Entry API for efficient in-place updates

    main

    The Entry API allows you to perform lookups and conditional insertions in a single operation, minimizing hashing overhead.

    • entry(key): Returns an Entry enum (Occupied or Vacant).
    • or_insert(default): Inserts the default value if the key is vacant and returns a mutable reference to the value.
    • or_insert_with(f): Inserts a value produced by a closure if the key is vacant.
    • and_modify(f): If the entry is Occupied, calls the closure f to modify the value in place.

    Example:

    let mut map: HashMap<&str, i32> = hashmap! {"bar" => 5};
    map.entry("foo").and_modify(|v| *v += 5).or_insert(1);
  5. Use `HashSet` for immutable hash sets

    main

    The HashSet<A> is an immutable hash set implementation using Hash Array Mapped Tries (HAMT). It is a generic set where values must implement Hash and Eq. Most operations are $O(\log_{x} n)$, which is nearly $O(1)$ for most sets.

    By default, HashSet<A> uses std::collections::hash_map::RandomState as the hasher and DefaultSharedPtr as the pointer type.

    Key characteristics:

    • Immutability: Operations like update or without return a new set rather than mutating the existing one in place (though insert and remove do provide in-place mutation for the local instance).
    • Predictable Order: Values have a predictable order based on the hasher used, though this is not insertion or sort order.
    • Efficiency: Cloning is $O(1)$ due to the underlying structural sharing.
    # #[macro_use] extern crate imbl;
    # use imbl::HashSet;
    # fn main() {
    # assert_eq!(
    #   hashset![1, 2, 3],
    #   HashSet::from(vec![1, 2, 3])
    # );
    # }
  6. Use `OrdMap` for ordered, immutable key-value storage

    main
    An OrdMap is an immutable ordered map implemented as a B+ tree. Unlike a HashMap, it only requires the Ord trait for keys and guarantees that keys are returned in ascending order. Most operations have $O(\log n)$ time complexity.
  7. Thread safety and shared pointers in imbl

    main

    By default, imbl data structures are thread-safe because they use std::sync::Arc for shared pointers.

    If you require higher performance and do not need thread safety, you can use non-thread-safe Rc pointers. This can be achieved by using GenericVector<T, shared_pointer::RcK> or by creating your own type alias using the archery crate logic.

  8. Use OrdSet for immutable ordered sets

    main

    An OrdSet<A> is an immutable ordered set implemented as a B+ tree. It is a specialized version of GenericOrdSet using DefaultSharedPtr.

    Use OrdSet when you need values to be returned in order (lowest to highest) or when you only want to require an Ord constraint on values rather than Hash + Eq. For better performance where ordering is not required, use GenericHashSet instead.

    Most operations are $O(\log n)$.

    use imbl::ordset::OrdSet;
    
    let set = OrdSet::unit(123);
    assert!(set.contains(&123));
  9. Use `Focus` for high-performance vector indexing

    main

    A Focus provides optimized indexing over a Vector. It works by caching the last accessed tree node and the path taken to reach it, making lookups for adjacent or nearby indices significantly faster than standard index lookups.

    When to use Focus:

    • Batching lookups: When performing a large number of index lookups that are likely to be close to each other.
    • Random access performance: Even for random access, Focus often provides a performance gain over standard vec.get(i) calls.

    When NOT to use Focus:

    • Sequential iteration: If you are just iterating forwards or backwards, use a standard Vector::iter() instead. Iterators are implemented using Focus internally but provide a simpler, more idiomatic interface.
    • Small vectors: For vectors with a length below the internal RRB tree branching factor (64), the setup cost of Focus may outweigh the benefits.
    • Few lookups: If you only need to perform a very small number of lookups, the overhead is not worth it.
    # #[macro_use] extern crate imbl;
    # use imbl::vector::Vector;
    # use std::iter::FromIterator;
    let mut vec: Vector<i64> = Vector::from_iter(0..1000);
    
    // Summing a vector faster using a Focus:
    let mut sum = 0;
    let mut focus = vec.focus();
    for i in 0..1000 {
        sum += *focus.get(i).unwrap();
    }
    assert_eq!(499500, sum);
  10. Manipulate vector sub-ranges with `Focus::narrow` and `Focus::split_at`

    main

    You can create restricted views into a vector using a Focus without modifying the underlying data structure.

    narrow(range)

    Creates a new Focus that only covers the specified range. This is functionally equivalent to a slice operation (&slice[range]).

    • Panics: If the range is not fully contained within the current focus.

    split_at(index)

    Consumes the current focus and returns a tuple of two new foci: one covering 0..index and the other covering index..N (where N is the current focus length). This is the moral equivalent of slice::split_at.

    • Panics: If the index is out of bounds.
    # #[macro_use] extern crate imbl;
    # use imbl::vector::Vector;
    # use std::iter::FromIterator;
    let vec: Vector<i64> = Vector::from_iter(0..1000);
    
    // Narrowing
    let narrowed = vec.focus().narrow(100..200);
    
    // Splitting
    let (left, right) = vec.focus().split_at(500);
  11. Configure imbl feature flags

    main

    You can enable additional functionality in imbl by adding specific features to your Cargo.toml:

    FeatureDescription
    proptestStrategies for all imbl datatypes under a imbl::vector::proptest namespace
    quickcheckquickcheck::Arbitrary implementations for all imbl datatypes
    rayonParallel iterator implementations for Vector
    serdeSerialize and Deserialize implementations for all imbl datatypes
    bincodeEncode and Decode implementations for all imbl datatypes
    arbitraryarbitrary::Arbitrary implementations for all imbl datatypes
    triompheUse triomphe::Arc as the default shared pointer (faster than std::sync::Arc in some cases)
    [dependencies]
    imbl = { version = "*", features = ["proptest", "serde", "bincode"] }