maplit

Rust macros that let you write HashMap, HashSet, BTreeMap, and BTreeSet literals inline, without the boilerplate of manual insert calls.

Library
Cargo
v1.0.2
402stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
30/100Needs Attention
Development Activity0
Maintenance0
Community40
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
53/100Fair
Architecture60
Code Quality60
Innovation45
Learning Curve45

maplit is a small, dependency-free Rust crate that adds container literal syntax the language itself doesn’t provide. Where vec![1, 2, 3] builds a Vec in one expression, maplit’s hashmap!{ "a" => 1, "b" => 2 } does the same for HashMap, and matching macros (hashset!, btreemap!, btreeset!) cover the other standard-library collection types.

Each macro expands at compile time to a sequence of insert calls, pre-sized using a @count arm so the resulting map or set doesn’t reallocate as it fills. A separate convert_args! macro wraps any of the four (or compatible third-party macros) to apply an Into-based or custom conversion function to keys and/or values, so a literal built from &str can populate a HashMap<String, String> without manual .to_string() calls at every pair.

What You Get

  • hashmap! and hashset! macros for building std::collections::HashMap and HashSet in one expression
  • btreemap! and btreeset! macros for the ordered BTreeMap and BTreeSet equivalents
  • convert_args! to apply a key/value conversion function (default: Into::into) while building any of the above
  • Capacity pre-allocation computed from the literal’s element count, avoiding reallocation during insertion
  • Optional trailing commas on every macro, matching Rust’s own array and vec! literal conventions

Common Use Cases

  • Building expected HashMap/HashSet values inline in unit tests for assert_eq! comparisons
  • Writing small static-like lookup tables (error codes to messages, flags to labels) as a single expression
  • Keeping doctest and example code short when a function needs a map or set argument
  • Constructing a HashMap<String, String> or similar owned-type container directly from &str literals via convert_args!

Under The Hood

Architecture The entire crate is a single src/lib.rs (roughly 160 lines) exporting four sibling macro_rules! macros — hashmap!, hashset!, btreemap!, btreeset! — plus convert_args! and a hidden __id identity function used as the default no-op conversion. There are no modules, no runtime state, and no dependency injection to speak of: each container macro recurses through internal @single/@count arms to compute an element count before allocating, then emits a block of insert calls. convert_args! follows a similar recursive-expansion pattern, filling in default keys=/values= conversion functions (defaulting to std::convert::Into::into) before re-invoking the wrapped macro with converted arguments. Because everything resolves at compile time via #[macro_export(local_inner_macros)], the public surface has no runtime behavior of its own beyond what the expanded insert calls do, which keeps the blast radius of any internal change very small.

Tech Stack maplit has zero external dependencies — its Cargo.toml declares none — and touches only std::collections::{HashMap, HashSet, BTreeMap, BTreeSet} directly. There’s no async runtime, ORM, or CLI layer; the crate’s only “deployment target” is being pulled in as a [dependencies] entry from crates.io. CI (.travis.yml) runs cargo build, cargo test, and cargo doc across a matrix from Rust 1.14.0 through nightly, and additionally builds two auxiliary crates (rust2015user/, rust2018user/) whose sole purpose is verifying the macros still resolve correctly under both the 2015 and 2018 editions’ macro-import rules.

Code Quality Tests exist in two places: inline #[test] functions at the bottom of src/lib.rs covering nesting, empty containers, and convert_args! interaction, and a separate tests/tests.rs exercising parsing edge cases such as trailing commas, expression keys, and a 200-plus-element hashset! invocation guarding against macro recursion limits. Because the macros can’t fail at runtime beyond ordinary insert semantics, there’s no error-handling surface to speak of, and naming is consistent (each macro mirrors its std container’s name). CI runs build/test/doc across Rust versions and editions but has no clippy or rustfmt step, and there’s no coverage tooling.

What Makes It Unique The crate’s small technical edge is precomputing element count in a @count macro arm so the resulting collection is pre-sized before insertion, avoiding the reallocation a naive sequence of .insert() calls (or a .collect() from an iterator) would incur. convert_args! is the more distinctive piece: it generically wraps any of the four container macros — or any third-party macro with a matching signature — to inject a per-key and per-value conversion function at macro-expansion time. Neither idea is unique to maplit; several Rust crates offer comparable container-literal macros, so this is a well-executed instance of an established pattern rather than something novel to the ecosystem.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search