Competitive analysis
This document compares adotenv with common Rust .env parsers and loaders. The
goal is to identify concrete work, not to rank libraries on a single number.
Comparison set
adotenv0.10.4dotenvy0.15.7dotenv0.15.0dotenv-compat0.4.0dotenv-ng-core1.0.0dotenv-parser0.1.3dotenv-linter4.0.0 for CLI comparison
Method
Benchmarks use a release build on Linux x86_64. The fixture has 65,536 simple
KEY=value entries and a 1.4 MB source. Each library reads the source before
measurement, parses it, and builds a map where the library supports one.
- Reference host: kbench, Intel Core i7-6700HQ, 4 cores and 8 threads, 2.6 GHz, Linux x86_64, rustc 1.97.0. The benchmark process was pinned to one CPU.
- Wall time:
hyperfine, ten process runs, multiple parses per run, using the median process time. CPU time is recorded separately. - Instructions: Valgrind callgrind, one parse, with a no-op baseline.
- Memory:
/proc/self/statushigh-water mark and retained RSS, with a no-op baseline. - Binary size: stripped release binary with thin LTO for a parse-and-print program.
- Dependencies and MSRV: crates.io metadata.
Run python3 scripts/compare.py --update-doc to regenerate the benchmark
section on an idle host. The tables below were recorded on kbench. Raw results
are stored in benchmarks/compare/results.json.
Results
Generated by python3 scripts/compare.py on kbench with rustc 1.97.0 (2d8144b78 2026-07-07).
Parse and map build
Per parse on the 65,536-entry fixture:
| Implementation | Wall time | CPU time | Instructions | Peak RSS | Retained RSS |
|---|---|---|---|---|---|
| adotenv parse | 34.9 ms | 34.8 ms | 207M | 26.5 MB | 26.5 MB |
| adotenv parse + values | 54.4 ms | 54.2 ms | 366M | 10.0 MB | 10.0 MB |
| adotenv parse + map | 42.4 ms | 42.3 ms | 277M | 32.6 MB | 14.2 MB |
| dotenvy iterator + map | 68.7 ms | 68.5 ms | 361M | 18.7 MB | 11.2 MB |
| dotenv-compat parse | 50.0 ms | 49.8 ms | 272M | 19.9 MB | 14.5 MB |
| dotenv-ng input-only | 93.7 ms | 93.4 ms | 384M | 26.2 MB | 20.2 MB |
| dotenv-parser map | 162.2 ms | 161.9 ms | 1242M | 27.2 MB | 10.6 MB |
Small-file parsing, in microseconds per parse:
| Implementation | 128 entries |
|---|---|
| adotenv parse | 52.2 us |
| adotenv parse + values | 76.8 us |
| adotenv parse + map | 70.8 us |
| dotenvy iterator + map | 106.9 us |
| dotenv-compat parse | 78.1 us |
| dotenv-ng input-only | 105.7 us |
| dotenv-parser map | 269.5 us |
Mixed fixture
Per parse on a 4,096-block mixed fixture with quoting, spacing, assigned values, Unicode and comments:
| Implementation | Wall time | CPU time | Instructions | Peak RSS | Retained RSS |
|---|---|---|---|---|---|
| adotenv parse | 20.6 ms | 20.5 ms | 106M | 19.5 MB | 11.0 MB |
| adotenv parse + values | 22.7 ms | 22.6 ms | 139M | 3.8 MB | 3.8 MB |
| adotenv parse + map | 27.6 ms | 27.5 ms | 153M | 19.2 MB | 5.8 MB |
| dotenvy iterator + map | 25.8 ms | 25.8 ms | 158M | 5.6 MB | 4.2 MB |
| dotenv-compat parse | 17.7 ms | 17.6 ms | 108M | 7.4 MB | 4.8 MB |
| dotenv-ng input-only | 24.5 ms | 24.4 ms | 137M | 7.6 MB | 6.1 MB |
| dotenv-parser map | 63.7 ms | 63.6 ms | 471M | 11.7 MB | 4.1 MB |
Loader profiles
Per load on the 65,536-entry fixture:
| Profile | Wall time | CPU time | Instructions | Peak RSS | Retained RSS |
|---|---|---|---|---|---|
| values-only load | 54.9 ms | 54.8 ms | 366M | 11.2 MB | 9.9 MB |
| default load | 73.9 ms | 73.7 ms | 465M | 47.7 MB | 27.9 MB |
| full-report load | 120.1 ms | 119.7 ms | 687M | 57.5 MB | 37.7 MB |
Real-world corpus
Per parse on the pinned public corpus, repeated with unique key prefixes:
| Implementation | Wall time | CPU time | Instructions | Peak RSS | Retained RSS |
|---|---|---|---|---|---|
| adotenv parse | 9.4 ms | 9.4 ms | 63M | 7.7 MB | 4.2 MB |
| adotenv parse + values | 9.5 ms | 9.5 ms | 66M | 1.2 MB | 1.2 MB |
| adotenv parse + map | 11.2 ms | 11.2 ms | 78M | 8.3 MB | 2.4 MB |
| dotenvy iterator + map | 11.4 ms | 11.3 ms | 72M | 2.7 MB | 1.7 MB |
| dotenv-compat parse | 7.8 ms | 7.8 ms | 56M | 4.0 MB | 2.0 MB |
| dotenv-ng input-only | 10.9 ms | 10.9 ms | 72M | 3.6 MB | 2.8 MB |
| dotenv-parser map | 36.1 ms | 36.0 ms | 277M | 6.1 MB | 1.4 MB |
Dialect matrix
Per parse on the 65,536-entry simple fixture:
| Dialect | Wall time | CPU time | Instructions | Peak RSS | Retained RSS |
|---|---|---|---|---|---|
| Node | 60.6 ms | 60.4 ms | 423M | 32.8 MB | 14.3 MB |
| Python | 50.6 ms | 50.4 ms | 329M | 32.6 MB | 14.3 MB |
| Compose | 51.7 ms | 51.5 ms | 305M | 32.5 MB | 14.2 MB |
| Docker | 42.7 ms | 42.5 ms | 269M | 32.6 MB | 14.3 MB |
Parse-only binaries
| Binary | Stripped size |
|---|---|
| adotenv | 454 KB |
| dotenv_compat | 367 KB |
| dotenv_ng | 404 KB |
| dotenv_parser | 450 KB |
| dotenvy | 386 KB |
Wall time varies with host load. Instruction counts are the stable CPU metric. adotenv parse uses fewer instructions than every measured alternative. Its parse-plus-map path uses more instructions than dotenv-compat and fewer than dotenvy. adotenv also retains more document memory because it keeps spans, items and diagnostics. The values-only projection path is the correct comparison for applications that do not need provenance.
Footprint
| Crate | Required normal dependencies | MSRV |
|---|---|---|
| adotenv | 0 | 1.81 |
| dotenvy | 0 | 1.56 |
| dotenv | 0 | not declared |
| dotenv-compat | 4 | 1.85 |
| dotenv-ng-core | 0 | 1.74 |
| dotenv-parser | 2 | not declared |
adotenv has no required runtime dependencies and supports no_std with alloc.
Its parse-only binary is within about 22 percent of the smallest measured binary.
Capability matrix
| Capability | adotenv | dotenvy | dotenv | dotenv-compat | dotenv-ng | dotenv-parser |
|---|---|---|---|---|---|---|
| Literal parse | Yes | Yes | Yes | Yes | Yes | Yes |
| Map or iterator API | Both | Iterator | Loader | Map | Map | Map |
| Lossless source | Yes | No | No | No | No | No |
| Token spans and positions | Yes | No | No | No | No | No |
| Recovery diagnostics | Yes | Limited | No | Limited | Errors | Errors |
| Multiple syntax presets | 5 | 1 | 1 | Node | 1 | 1 |
| Verified edits and formatting | Yes | No | No | No | No | No |
| Sort, compare and examples | Yes | No | No | No | No | No |
| Reference inspection | Yes | No | No | No | No | No |
| Explicit resolution | Yes | No | No | No | Substitution | No |
| Cascade loading and provenance | Yes | Search/load | Search/load | Files | Multiple files | No |
| Values-only projection | Yes | No | No | No | No | No |
| Serde | Yes | No | No | No | No | No |
| CLI | Yes | No | No | No | No | No |
no_std | Yes | No | No | No | No | No |
dotenv-linter is a separate CLI category. It provides lint rules and fixes but
does not expose adotenv’s library model or loader provenance.
Correctness
adotenv has a pinned conformance corpus for Node, Python, Compose and Docker behavior. It also has parser, edit, encoding, resolution and formatting fuzz targets.
The main gap is breadth, not absence. Add more pinned symbols and differential checks against Node and Python, especially for malformed and multiline cases. Keep every observation tied to a source identity and input hash.
Documentation
adotenv has 20 public documents covering architecture, dialects, edits, formatting, resolution, loading, serde, CLI, integrations, quality and performance. Examples in the README are doctested.
Gaps:
- no dedicated documentation site;
- no guided tutorial or task-based examples;
- design examples are not compiled;
- no searchable API-to-task index;
- no published benchmark dashboard;
adotenv.github.iois unavailable because there is noadotenvGitHub organization.
A project site can use asamarts.github.io/adotenv now, or a custom domain, or
an adotenv organization if that name can be created.
API assessment
Strengths:
- one parse result supports inspection, edits, comparison and formatting;
- policy is explicit and composable;
- errors and diagnostics are structured;
- loading and process mutation stay opt-in;
- values, origins and reports are borrowed views.
Risks:
- the public surface is broad for new users;
- similar concepts use several types (
Document,Entry,Item, views,LoadedEnvironment,ResolvedEnvironment); - examples are spread across the README and design docs;
- the migration from parse to edit to load is not presented as a single guided path.
The next step is a task-based API audit, not a redesign.
Priorities
- Commit a reproducible comparison harness and keep these numbers current.
- Build a documentation site with a tutorial, task guides and search.
- Improve values-only performance and memory measurements against the same fixture.
- Expand conformance and differential tests.
- Audit common-task APIs and consolidate examples.
- Close feature gaps only where they fit the literal, explicit design. File encryption, implicit environment search and ambient command execution are not automatic goals.
- Complete the 1.0 evidence for fuzzing and external adoption.