# arximboldi/immer: Persistent and Immutable Data Structures for C++

> immer is a header-only C++14 library of persistent data structures with structural sharing. It suits C++ codebases that need cheap snapshots and value semantics, and it is a poor fit if you want a drop-in replacement for std::vector mutation.

**arximboldi/immer** — Postmodern immutable and persistent data structures for C++ — value semantics at scale

- Repository: https://github.com/arximboldi/immer
- Website: https://sinusoid.es/immer
- Stars: 2,871 · Forks: 204
- Language: C++
- License: BSL-1.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/arximboldi-immer

## What problem immer solves, and for whom

The library targets C++ developers who need values that never change after construction but still want updates to be cheap. A persistent data structure keeps the old version alive when you produce a new one, and structural sharing means the two versions point at most of the same memory. The README frames this around three properties: interactivity, concurrency and parallelism. New values can be compared with old ones cheaply, immutable data can be read from multiple concurrent processes without copying, and some of the structures support O(log(n)) concatenation.

The audience is narrower than "all C++ programmers". If you are writing a text editor, a UI state store, a compiler pass that produces new intermediate representations, or anything with an undo stack, the copy-on-write model pays for itself. If you are writing a numeric kernel that fills one buffer in a loop, immer adds indirection and buys nothing. The README is explicit that the library is written in C++14 and that a compliant compiler is necessary, with continuous testing against Clang 3.8 and GCC 6.

## How structural sharing and value semantics actually work

immer containers are immutable: every operation that looks like a mutation returns a new container. The README example shows this with immer::vector, where v0.push_back(13) yields v1 and leaves v0 with size 0. The internal representation is a tree, and the topics list names the two families involved: HAMT (hash array mapped trie) for the map and set types, and RRB-tree for the vector and flexible vector types. A push_back or set walks the tree, copies only the nodes along the path, and shares every untouched subtree with the previous version.

That is the whole trick, and it has a cost you should price in. Reads go through tree traversal instead of contiguous indexing, so a persistent vector is not the same thing as a std::vector with a different API. The library compensates with what the README calls policy-based design: templates let you choose memory management strategies, which the documentation covers on its own memory page. This is also why immer is described as a foundation for higher-level languages with a C runtime, such as Python or Guile, rather than only an end-user container library.

One design consequence worth stating plainly: because old versions stay reachable, memory is reclaimed only when the last reference to a version goes away. The justfile contains a valgrind target with --leak-check=full and --errors-for-leak-kinds=all, which suggests the maintainers treat retained memory as something to verify rather than assume.

## Installing immer and running a first vector example

The README states the library is header only: you can copy the immer subfolder somewhere in your include path. There is no compiled library to link and no external dependency. If you use the Nix package manager, which the README says it strongly recommends, the install is a single command:

```bash
nix-env -if https://github.com/arximboldi/immer/archive/master.tar.gz
```

Alternatively, the README shows cloning the repository and using CMake to install it system-wide. The snippet in the README begins with creating a build directory, and the repository's justfile shows the CMake options used for its own builds, including -Dimmer_BUILD_TESTS=ON and -Dimmer_BUILD_EXAMPLES=OFF, so those are the real switch names.

```bash
mkdir -p build && cd build
cmake ..
```

Once the headers are reachable, the smallest useful program is the one from example/vector/intro.cpp. It constructs an empty vector, appends a value, then sets index 0 on the new version and asserts that the earlier version is unchanged. Compile it with your normal C++14 flags and the include path pointing at the directory that contains immer/.

```cpp
#include <immer/vector.hpp>

int main()
{
    const auto v0 = immer::vector<int>{};
    const auto v1 = v0.push_back(13);
    assert(v0.size() == 0 && v1.size() == 1 && v1[0] == 13);

    const auto v2 = v1.set(0, 42);
    assert(v1[0] == 13 && v2[0] == 42);
}
```

If both assertions pass, you have confirmed the two properties that matter: push_back and set return new values, and the old values are untouched. The repository also ships per-container examples under example/array, example/box, example/flex-vector, example/map, example/set, example/table and example/vector, and a didactic text editor called Ewig is linked from the README as a complete worked example.

## Where immer is the wrong tool

The failure mode is treating immer::vector as a faster std::vector. It is not. For a workload that appends a million elements to one container and then reads them in order, a contiguous std::vector will beat a tree-backed persistent vector on both memory and cache behaviour, and the README makes no claim otherwise. The persistent version wins only when you need the intermediate versions to survive.

A second constraint is compiler and standard support. The README says the library is written in C++14 and has been continuously tested with Clang 3.8 and GCC 6, and notes it might work with other compilers and versions. "Might" is doing real work in that sentence. If your toolchain is older than that, or if you are on a compiler the project does not test against, you are on your own. The justfile also builds its own debug configurations with -DCXX_STANDARD=17, so the tested surface is not identical to the minimum supported surface.

A third issue is that a header-only library with policy templates pushes compile times and error messages onto you. Template instantiation errors from deeply nested policy types are hard to read, and there is no way for the library to avoid that. Finally, the README does not document serialization, thread-safety guarantees per container, or rollback of a partially applied change. If your design depends on any of those, verify them in the headers before you build on top of them.

## immer versus Immutable.js and other persistent collections

The obvious comparison is with the JavaScript persistent collection libraries the README itself names: Mori and Immutable.js, used with React and similar UI frameworks. The data-structure ideas are the same family, HAMT and RRB-tree, but the difference in approach is memory layout. JavaScript engines give those libraries a garbage collector and a uniform object model; immer is C++ and uses templates and policy-based design so that allocation strategy is a compile-time choice. That is why the README argues the library can be a foundation for languages with a C runtime, and why the same structures can be tuned for a specific allocator.

Against a hand-rolled copy-on-write wrapper around std::vector or std::unordered_map, the difference is asymptotic and structural. A naive wrapper copies the whole container on write, which is O(n); immer copies the path, which is O(log(n)) for the trie-backed types. Whether that matters depends entirely on container size and update frequency. For small containers, the constant factors of a tree can erase the advantage. For large ones with frequent updates and retained history, the gap is the reason to adopt the library at all. Note that the search data around this project's name is dominated by the JavaScript library called Immer (the produce function, React integration, the npm package). That is a different project, and none of its API applies here.

## Licence, maintenance and the cost of upgrading

immer is distributed under the Boost Software License 1.0, identified as BSL-1.0. The source files carry the standard header pointing at the LICENSE file, and setup.py repeats it in its comment block. BSL-1.0 is a permissive licence, but this is not legal advice; check the LICENSE file and your own obligations rather than relying on a summary.

The repository is not archived, and the last push was on 2026-07-05. Releases are infrequent and unevenly spaced: v0.8.1 on 2023-10-03, v0.9.0 on 2025-12-10, and v0.9.1 on 2026-01-13. Between v0.8.1 and v0.9.0 there is a gap of more than two years, which tells you that pinning to a release tag means long stretches without a new one. If you vendor the headers, as the header-only design invites, you also take on the job of tracking changes yourself.

That is the real upgrade cost. There is no documented deprecation policy in the README, and no migration guide for the 0.8 to 0.9 jump is described there. Before adopting, decide whether you consume immer through a package manager such as Nix, through CMake, or by copying the immer subfolder, because each choice changes how much work a version bump costs you. The repository's justfile shows the maintainers run ASAN and valgrind builds, which is a signal about how they test, not a promise about what your upgrade will break.

## Conclusion

Adopt immer if your C++ code needs cheap snapshots, undo history or lock-free reads of shared state, and you can accept a new container vocabulary and a C++14 toolchain. Skip it if your workload is append-only mutation of one long-lived buffer, where a plain std::vector is faster and simpler. Before committing, build the example from example/vector/intro.cpp against your own compiler and confirm that the immer/ headers compile cleanly in your build system.

## FAQ

### What is the arximboldi/immer C++ library?

It is a header-only C++14 library of persistent and immutable data structures with structural sharing. The README describes it as enabling new architectures for interactive and concurrent programs, and the repository lists HAMT and RRB-tree implementations among its topics.

### How do I install immer?

The README says it is header only, so you can copy the immer subfolder into your include path. With the Nix package manager, which the README strongly recommends, you can run nix-env -if with the repository tarball URL, or clone the repository and install it with CMake.

### Is immer the same as the JavaScript Immer library?

No. arximboldi/immer is a C++ library of persistent data structures, while the JavaScript Immer library with its produce function and React integration is a separate project. The README here never mentions a produce function or npm.

### Does immer work with any C++ compiler?

The README states the library is written in C++14 and has been continuously tested with Clang 3.8 and GCC 6, adding that it might work with other compilers and versions. That wording means other toolchains are not guaranteed.

### What data structures does immer provide?

The repository topics name HAMT and RRB-tree, which back the map and set types and the vector and flex-vector types respectively. The example/ directory contains per-container examples for array, box, flex-vector, map, set, table and vector.

### What licence does immer use?

It is distributed under the Boost Software License 1.0, shown as BSL-1.0, and the source file headers point to the LICENSE file in the repository. This is a description of the licence identifier, not legal advice.

## Sources

- [arximboldi/immer on GitHub](https://github.com/arximboldi/immer)
- [License: BSL-1.0](https://github.com/arximboldi/immer/blob/master/LICENSE)
- [Project website](https://sinusoid.es/immer)
- [README](https://github.com/arximboldi/immer/blob/master/README.md)
- [Releases](https://github.com/arximboldi/immer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/arximboldi-immer
