Library / SDK
biojppm/rapidyaml avatar
biojppm/rapidyaml

rapidyaml: a YAML parser with no standard library containers underneath

Rapid YAML - a library to parse and emit YAML, and do it fast.

761 stars143 forksC++MIT

At a glance

What is it?
Rapid YAML, or ryml, is a C++11 library to parse and emit YAML, built on a flat index-based tree of string views, a non-recursive state machine parser and custom allocators, with a CI matrix that covers bare-metal chips and dozens of CPU architectures. It suits C++ codebases that cannot afford a heavy YAML dependency, and it does not suit teams that want a batteries-included object mapping layer.
Who is it for?
rapidyaml is the right choice for a C++ project whose YAML handling has to live inside a constrained environment, a game engine, or a service where the dependency footprint of a full YAML library is not acceptable, because the tree is a flat index-based structure over string views and the parser is a non-recursive state machine that the project runs under sanitizers, valgrind and clang-tidy across a wide architecture matrix.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

No STL containers, and that is the whole design

rapidyaml, short for ryml, is a C++ library to parse and emit YAML. Its distinguishing property is stated early and repeated in the technical notes: ryml does not depend on the STL, it does not use any standard container as part of its data structures, though it can serialize and deserialize those containers into the data tree.

That constraint is the reason the tree looks the way it does. Instead of building a heavy dictionary tree of allocated node objects, ryml parses into a flat, index-based data tree. The documentation positions this as the reason the library is efficient for massive datasets and for bare-metal systems, and the target range runs from x64 down to chips with no operating system at all.

The library is written in C++11 and the documented compiler floor is deliberately old: Visual Studio 2015 and later, clang++ 3.9 and later, g++ 4.8 and later, plus the Intel Compiler. A library that still compiles with a g++ from 2013 is making a specific promise, which is that it will not force you to raise your toolchain floor.

It also ships with parts of c4core, described as a small multiplatform C++ utilities library, and that dependency is a deliberate choice rather than an accident: the in-source version can be ignored in favour of a custom or system-installed c4core.

Views into the source buffer, so parsing copies nothing

ryml parses both read-only and in-place source buffers, and the resulting tree holds only views to sub-ranges of the source buffer. No string copies or duplications are done, and no virtual functions are used. The tree representation internally stores only string views and assumes nothing about user types.

The practical consequence is that a parsed document refers back into the bytes you handed it. If you parse a buffer and then free or modify that buffer, the strings in the tree become invalid. That is the trade every zero-copy parser makes, and it means the lifetime of your source buffer becomes part of your API contract. The in-place mode goes further: ryml can parse into a buffer it is allowed to modify, which is where the name of the in-place parsing path comes from and where the memory savings are largest.

The performance claim is that serialization is not just fast but extremely fast, in many cases faster than the fastest C++ facilities like std::to_chars(). The project backs this with published benchmark plots and scatter plots across YAML files of different styles and sizes, comparing ryml against yaml-cpp, libyaml and fyaml, using its own example YAML file. Those numbers come from the project's own benchmark harness, and the plots are linked from a separate results repository rather than reproduced in the documentation.

Serialization is something you ask for, not something that happens

The other half of the design is when work happens. With the tree, deserialization and serialization occur only at your direct request, after parsing or before emitting. In other words, the library does not populate your own types for you while it parses. You get a tree, and you walk it or convert it yourself.

This is the difference from a binding-oriented library, and it is where a lot of the surprise lives for newcomers. There is no annotation-driven mapping from YAML keys to struct members, no automatic vector-of-struct deserialization, no schema validation. The project describes the tree as making it easy and fast to read and modify the data tree, and modification happens in place on the tree rather than on your domain objects.

For a C++ project with its own type system, that arrangement is often the right one. It keeps the library out of your header files, it means you decide what a YAML document means for your domain, and it means a document with a field you do not recognise is not an error but simply a node you can ignore. The cost is that you write the conversion layer yourself, and that layer is code you own.

Custom allocators and an error handler that is allowed to longjmp

ryml can use custom global and per-tree memory allocators and error handler callbacks, and it is exception-agnostic. A default implementation is provided for both: the allocator uses std::malloc(), and the error handlers use either exceptions, longjmp() or std::abort(). You can opt out and supply your own memory allocation and your own exception-throwing callbacks.

The per-tree allocator is the interesting one, because it means two documents can be allocated from two different pools, which is how you keep a parse inside a memory budget or inside an arena that you free in one go. The error handler choice is the other half of the same story. A library whose default failure mode is std::abort() cannot be embedded in a service that wants to survive a malformed document, and a library that throws by default cannot be used where exceptions are disabled. ryml makes the decision explicit at the point of configuration rather than baking it in, which is the right call for code meant to run on bare metal and in large C++ codebases alike.

The upstream note about error handling is blunt about why this matters: the project positions these as things you can opt out of and provide your own. If your codebase has a house rule for error reporting, this is the knob to use.

The samples directory is the integration guide

The repository does not have a single install command, and the integration story is told through the samples directory instead. The top-level samples include add_subdirectory, custom_c4core, fetch_content, find_package, ints_only, singleheader, singleheader-ints, singleheaderlib, singleheaderlib-ints, singlesource, plus two quickstart programs, quickstart.cpp and quickstart-ints.cpp.

Read that list and the available shapes of adoption are clear. There is a CMake package to find with find_package, a subdirectory to add with add_subdirectory, a FetchContent route for a header-only style consumption, a single-header build for a project that wants one file on disk, a single-source build, a singleheaderlib variant that also produces a library, an ints_only variant for code that wants to keep the tree's string handling minimal, a custom_c4core sample for people who want to control that dependency, and two quickstarts for the first program.

The samples directory also tells you that the top-level tree carries CMakeLists.txt, api/, src/, src_extra/, test/, tools/, doc/, bm/ for benchmarks, ext/, proj/, a changelog directory, ROADMAP.md and tbump.toml for version bumping. The presence of a public api/ directory alongside the samples is a useful signal for a library you intend to depend on: the interface is separated from the implementation rather than exposed from the headers you include.

A CI matrix that is the real specification

The project's own description of its test pipeline is unusually specific, and it is the most useful evidence in the documentation. The CI is described as merciless and extremely demanding, covering Linux, Windows and MacOS. The tests include analysing ryml with valgrind, clang-tidy, and the gcc and clang sanitizers asan, ubsan and lsan.

The architecture coverage is listed in full: x64, x86, wasm through emscripten, aarch64, armv7, armv6, armv5, armv4, loongarch64, mips, mipsel, mips64, mips64el, riscv64, s390x, ppc, ppc64, ppc64le and sparc64. The project also links an issue documenting that ryml runs in bare-metal.

Read together with the wasm entry, that list explains the JavaScript availability: ryml can be compiled to JavaScript through emscripten. There is also a Python binding published on PyPI, with a secondary repository for it, and the topic tags on the repository reflect both.

The conformance claim is separate from the performance claim. The library is described as fully conformant to the YAML 1.2 specification and as passing all of the cases in the YAML test suite, with the caveat that there are some minor deliberate deviations in default behaviour, documented on a dedicated page in the reference documentation. That caveat is the one to read before you rely on edge cases, because the deviations are described as deliberate rather than accidental.

The code is also described as having no known vulnerabilities, and as extremely tested and fuzzed.

Where ryml is the wrong tool, and what to use instead

The clearest wrong-tool case is a team that wants YAML to become objects without writing the mapping layer. ryml deliberately stops at the tree, so a project that wants annotation-based binding should look at a binding-oriented library instead, where the trade is a heavier dependency and a less predictable memory profile for a much smaller amount of your own code.

The second case is conformance. The documented deviations from the YAML 1.2 specification, while described as minor, mean a document that other parsers accept might not round-trip identically here, and a parser that rejects a specific construct is a bad fit for a configuration format you do not control. If your input comes from outside your organisation, check the deviations page against the constructs your producers actually emit.

The third is a C++11 library with an old compiler floor and no STL containers, which is a real asset in embedded and constrained work and a real cost in a modern codebase where the surrounding code expects standard containers and exceptions everywhere. The migration is not a drop-in: you have to learn the tree API, and the custom allocator and error handler callbacks are additional concepts that a mainstream library would not ask of you.

A fourth limit is documentation surface. The project publishes a changelog and a roadmap, a reference documentation site and a set of samples, but the repository entry list shows no tutorial directory, so a newcomer learns the API from the samples and the reference pages rather than from a guided path.

Editorial conclusion

rapidyaml is the right choice for a C++ project whose YAML handling has to live inside a constrained environment, a game engine, or a service where the dependency footprint of a full YAML library is not acceptable, because the tree is a flat index-based structure over string views and the parser is a non-recursive state machine that the project runs under sanitizers, valgrind and clang-tidy across a wide architecture matrix. It is the wrong choice for a team that wants YAML bound to structs with annotations, or for a project that treats YAML 1.2 conformance as a hard requirement without reading the documented deviations. Before you commit, check the three things that decide the outcome: which of the integration routes in the samples directory you actually need, since add_subdirectory, find_package, FetchContent, single-header and single-source builds are all separate shapes of adoption; what your allocator and error handling contract looks like, because the default error handler aborts or longjmps and you may want to supply your own; and the release line itself, since the current version is 0.16.0 and the project is still below 1.0 while the last push was on 2026-10-21.

Frequently asked questions

How do I integrate rapidyaml into a C++ project?

The repository documents several routes through its samples directory rather than a single install command: add_subdirectory, find_package, fetch_content, a singleheader build, a single-source build, a singleheaderlib build, an ints_only variant and a custom_c4core sample, along with quickstart.cpp and quickstart-ints.cpp as first programs. The library also ships a CMakeLists.txt at the top level.

Does rapidyaml copy the YAML data into its own strings?

No. It parses read-only and in-place source buffers, and the tree holds only views to sub-ranges of the source buffer, with no string copies and no virtual functions. Serialization and deserialization happen only at your request, after parsing or before emitting.

Can I control memory allocation and error handling in rapidyaml?

Yes. ryml accepts custom global and per-tree memory allocators and error handler callbacks, and it is exception-agnostic. The defaults use std::malloc() for allocation and either exceptions, longjmp() or std::abort() for error handling, and you can supply your own allocation and exception-throwing callbacks.

Official sources

  1. biojppm/rapidyaml on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/biojppm-rapidyaml.svg)](https://hysenlabs.com/projects/biojppm-rapidyaml)

Was this useful?