release: v0.4.0 — from_json 反序列化 + 无墓碑 Robin Hood 删除 + 测试重组
- 新增公开 API:from_json / from_json_with(保插入序反序列化;触发 minor 升级)
- 修复 insert 重复键缺陷:墓碑删除改为回溯搬移(backshift_remove),统一穷尽 定位 locate,insert 与 rehash 共享 robin_hood_insert_into
- 测试重组(走向1):库内保留白盒+库内特有测试,黑盒健壮性测试移入 indexmap-test-suite
- CI 加固:check –deny-warn + target×mode 矩阵 + examples job
- VERSION / moon.mod / pkg.generated.mbti 升至 0.4.0;文档与 RELEASE_CHECKLIST 同步
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9
京公网安备 11010802047560号
moonbit-indexmap
A hash map that preserves insertion order — MoonBit port of Rust’s
indexmapcrate.MoonBit’s built-in
Map[K, V]preserves insertion order but offers no way to address entries by position. IndexMap pairs that insertion-order guarantee with index-based access (get_index,get_index_of,first,last,pop,swap_remove_index), an Entry API, and order-sensitiveEq/Hash, making it ideal for configuration parsing, JSON serialization, LRU caches, and deterministic tests.Features
get_index(i),first(),last(),pop()OccupiedEntry/VacantEntryfor in-place manipulationis_disjoint,is_subset,is_supersetToJsonpreserves key order;from_json/from_json_withdeserialize back (order-preserving, sofrom_json(m.to_json()) == mis a lossless round-trip forString-keyed maps)Debug,Default,Show,Hash,Eq,ToJsonfor both IndexMap and IndexSetArbitrarytrait for property-based testingInstallation
Add to
moon.mod:Or clone directly:
API Overview
IndexMap[K, V]
new(),with_capacity(n),from_array(entries),default(),copy()len(),is_empty(),capacity(),load_factor(),max_probe()insert(k, v) -> V?,get(k) -> V?,remove(k) -> V?,contains(k) -> Bool,clear(),get_mut(k, f)entry(k) -> EntryView(Occupied:get/insert/remove/key, Vacant:insert/key)get_index(i),get_full(k),get_index_of(k),first(),last(),pop(),swap_remove_index(i)reserve(n),shrink_to_fit()iter(),keys(),values(),for_each(f),into_iter(),into_array()retain(f),sort_by_key(),sort_by(cmp),drain(),extend_from_array(entries)Debug,Default,Show,Hash,Eq,ToJsonIndexSet[K]
new(),with_capacity(n),from_array(elements),default(),copy()len(),is_empty(),capacity()insert(v) -> Bool,contains(v) -> Bool,remove(v) -> Bool,clear()is_disjoint(other),is_subset(other),is_superset(other)iter(),into_array()retain(f),drain(),extend_from_array(elements)Debug,Default,Show,Hash,Eq,ToJsonDesign
Two parallel structures:
Array[Entry[K, V]?]) — O(1) average lookup, reduced probe varianceArray[K]) — tracks insertion order for deterministic iterationDeletion uses backward-shift compaction: displaced entries move back until the next entry is at its home bucket or the cluster ends. This preserves probe reachability without retaining dead bucket entries.
load_factor()therefore always reports live entries divided by capacity.Compared to built-in Map
Map[K, V]IndexMap[K, V]get_index,first,pop, …)Occupied/Vacant)Eq/HashsemanticsGotchas
Known design choices and limitations — see the independent test report for reproduction details.
get_mutsemantics: the callback’s return value is authoritative (reworked in v0.3.3).get_mut(key, f)passes the current value tof(orNoneif the key is absent) and then re-applies the result throughinsert/remove:Some(v)storesvunderkey(inserting it if the callback removed it), andNoneremoveskey. ReturningNonetherefore removes the key even if the callback re-inserted it — returnSome(v)to keep a value. Because the result is re-applied via a fresh probe, the callback may safely mutate the map (including triggering a resize). Earlier versions wrote back to a stale bucket index, which could corrupt the table and silently broke plain deletion.EqandHashare insertion-order-sensitive. Two maps with identical key-value pairs but different insertion orders are not equal and produce different hashes. Avoid using anIndexMaporIndexSetas a key in another hash container unless you can guarantee consistent insertion order.swap_remove_indexis actually O(n) shift-remove. Despite the name (kept for Rust indexmap API compatibility), it calls the order-preservingremovepath — elements after the target are shifted one slot left. It does not swap with the last element in O(1). If you need actual O(1) order-breaking removal, you would need a dedicated method that directly swaps with the last element before popping —swap_remove_indexdoes not do this.max_probe()is refreshed aftersort_by/sort_by_key(fixed in v0.3.2). Sorting rebuildsorder[]andpositions[]; as of v0.3.2 the internalmax_probe_distanceis also recalculated after sorting, somax_probe()reports the current (post-sort) probe distribution. (Sorting does not move buckets, so previously the value happened to remain correct — it is now maintained explicitly.)Don’t mutate the map while an iterator is active. Each iterator snapshots the map’s mutation version at creation; if the map is structurally modified (
insert,remove,clear,retain,sort_by*,reserve,shrink_to_fit, or an Entry /get_mutmutation) before the iterator is exhausted, the nextnext()aborts withIndexMap: map mutated during iteration— true fail-fast, added in v0.3.3. Earlier versions silently skipped entries and could crash with an out-of-bounds access. Finish all mutations first, then create a fresh iterator.Independent Test Report
An independent black-box test suite (
indexmap-test-suite) covers every public API, stress up to 100k entries, property-based invariants, edge-case traps, plus (as of the latest reorganization) HashDoS / adversarial collision, fail-fast iterator aborts, real benchmarks + a regression gate,from_jsonround-trip, and Rustindexmapdifferential tests. The library itself keeps the white-box + library-specific tests in-repo — the model/oracle property test, fuzz harness, and IndexMap-vs-builtin-Map parity (see CLAUDE.md for the per-file breakdown anddocs/RELEASE_CHECKLIST.mdfor the full Tier 0–4 status against the release checklist).Examples
The example packages live in
cmd/:cmd/lru_cache— LRU eviction democmd/config_parse— order-preserving config parsercmd/json_order—ToJsonkey orderingDevelopment
CI:
checkjob (fmt / check –deny-warn / mbti drift) + atarget × modetest matrix + anexamplesjob. The black-box robustness battery (HashDoS, fail-fast, perf, Rust differential, JSON round-trip) lives inindexmap-test-suite. See CONTRIBUTING.md for project layout, roadmap, and contribution guidelines.License
Apache 2.0 — see LICENSE.
Built for the MoonBit Open Source Ecosystem Competition 2026.