yaml-rust
A pure Rust YAML 1.2 parser and emitter with a dynamic Yaml enum and panic-free indexed access.
Repository Health
Technical Analysis
yaml-rust is a pure Rust implementation of the YAML 1.2 specification, built with no unsafe code and no C bindings. It parses YAML text into a dynamically-typed Yaml enum (Array, Hash, String, Integer, Real, Boolean, Null) that supports Ruby-like index access via doc["key"][0], and it can emit that same structure back to a YAML string.
The crate exposes both a high-level YamlLoader/YamlEmitter API for quick document parsing and dumping, and a lower-level, libyaml-influenced event stream (Parser, Scanner, Event) for consumers who need to react to tokens directly rather than build a full in-memory tree. It has shipped as the de facto pure-Rust YAML crate for years and remains widely depended upon even though active development has slowed.
What You Get
- A
YamlLoader::load_from_strentry point that parses a YAML string into aVec<Yaml>covering multi-document streams - A
Yamlenum with Ruby-likeIndex<&str>andIndex<usize>implementations, returningYaml::BadValueinstead of panicking on invalid access - A
YamlEmitterthat serializes aYamltree back into a YAML string for round-tripping documents - A lower-level
Parser/Scanner/EventAPI (via theEventReceiverandMarkedEventReceivertraits) for streaming, low-allocation consumption of YAML tokens - Typed accessor helpers (
as_str,as_i64,as_f64,as_bool,as_vec,as_hash) for pulling native Rust values out of a parsed node
Common Use Cases
- Reading application or CLI configuration files without defining a full serde-derived struct up front
- Building tools that need to inspect or transform arbitrary/unknown-shape YAML documents dynamically
- Round-tripping YAML through a parse-modify-emit cycle while preserving document structure
- Implementing YAML-based DSLs or manifest formats where the schema is not known statically
Under The Hood
Architecture
The crate is a straightforward four-module pipeline: scanner.rs tokenizes raw text into a VecDeque of typed tokens, parser.rs drives an explicit State enum (mirroring libyaml/yaml-cpp’s design) that turns those tokens into a stream of Events (DocumentStart, SequenceStart, Scalar, MappingEnd, and so on), yaml.rs’s YamlLoader implements the MarkedEventReceiver trait to fold that event stream into the public Yaml tree using a doc_stack/key_stack, and emitter.rs walks a Yaml tree back out to a string. Each stage only depends on the one before it, so a consumer can tap in at the event level (implementing EventReceiver directly) without ever materializing the full Yaml tree. The main fragility is that Yaml is a public, non-exhaustive-unmarked enum — anything matching on it directly breaks if a variant is ever added.
Tech Stack
A minimal Rust 2018-edition crate with a single runtime dependency, linked-hash-map (for order-preserving Yaml::Hash maps), and quickcheck as a dev-dependency for property-based round-trip testing. There is no unsafe code, no async runtime, no build script, and no FFI — the whole crate is safe, synchronous, in-memory string processing. CI runs on Travis (stable/beta/nightly plus a pinned 1.33.0 MSRV job and an optional clippy lint job) and AppVeyor for Windows coverage, though both predate the project’s move to GitHub Actions-era tooling and the crate itself has been effectively feature-frozen since its 0.4.5 release.
Code Quality
Testing is spread across three layers: inline #[cfg(test)] unit tests in lib.rs, a tests/spec_test.rs suite that replays YAML-test-suite-style events against the low-level parser, and tests/test_round_trip.rs, which asserts that parsing and re-emitting a document is lossless for tricky cases like escape characters, colons inside strings, and number-like quoted strings (several of which are direct regressions from filed GitHub issues). Errors are typed and returned as Result (ScanError, EmitError) rather than panicking in library code, and #[cfg_attr(feature = "cargo-clippy", ...)] attributes show clippy was run against the codebase, though there’s no rustfmt.toml or CI-enforced formatting check.
API Design
The API optimizes for zero-ceremony reads: YamlLoader::load_from_str(s) plus chained doc["key"][0] indexing gets you into a document with no struct definitions, and invalid paths degrade to Yaml::BadValue instead of panicking, which keeps exploratory or partially-known-schema code simple. The tradeoff is that consumers wanting compile-time-checked structures need to hand-write the as_* conversions themselves, since this crate predates (and is now largely superseded by) serde-integrated alternatives like serde_yaml and its own community fork yaml-rust2.