Contents
Getting started
- Install — stop-gap shim usage
- Security: RUSTSEC-2025-0068 fixed in 0.0.13
- Quick Start — shim usage in ten lines
Choosing a replacement
- Maintained alternatives — picking a destination crate
- One-minute migration paths — diff snippets per destination
Deprecation reference
- What changed in 0.0.13 — the shim, in one paragraph
- What still works in 0.0.13 — surviving tests and examples
- What was removed in 0.0.13 — the C-FFI surface
- Behavioural notes — two intentional deltas worth knowing
Operational
- MSRV — Rust 1.85.0 floor
- Documentation — migration guide, alternative-crate docs
- License
Install
serde_yml = "0.0.13" is a stop-gap so an in-flight migration
doesn't block your release. Existing call sites compile unchanged;
the compiler emits a deprecation warning at each use serde_yml::*
import pointing at the migration guide.
[]
= "0.0.13"
The shim itself depends on noyalib's compat-serde-yaml feature
for its implementation. The previous C-FFI parser (libyml) and
serde_yaml 0.9 are no longer in the dependency graph. Whether
your eventual destination is noyalib or one of the other
maintained alternatives is your call.
Security: RUSTSEC-2025-0068 fixed in 0.0.13
RUSTSEC-2025-0068
(also GHSA-hhw4-xg65-fp2x)
flagged all serde_yml versions ≤ 0.0.12 as unsound — the
serde_yml::ser::Serializer.emitter field could cause a
segmentation fault. The advisory is informational severity but
real: it stems from the C-FFI libyaml parser the original crate
linked against.
Upgrading to serde_yml = "0.0.13" removes the vulnerable
surface entirely:
- The C-FFI
libymldependency is gone from the graph. serde_yml::ser::Serializeris now a re-export of a pure-Rust unit struct (pub struct Serializer;) with noemitterfield. Any code that referenced.emitterwon't compile — which is exactly the desired outcome.- The pure-Rust backend (
noyalib) enforces#![forbid(unsafe_code)]across the whole workspace, so the underlying unsoundness class cannot recur through the shim.
Verification:
# RUSTSEC-2025-0068 no longer flagged for serde_yml — the
# vulnerable surface is not present in 0.0.13.
|
# (no output — libyml is not in the dependency graph)
The same fix flows through to any
maintained alternative you eventually
migrate to. Pinning serde_yml = "=0.0.12" keeps the advisory in
your audit feed; pinning serde_yml = "0.0.13" (or migrating
directly) clears it.
Quick Start
use ;
Run the bundled examples with cargo run --example example (the
aggregator) or cargo run --example migration (a single-file demo).
Maintained alternatives
Three crates are realistic destinations for a serde_yml user.
None is being prescribed — pick the one that fits the codebase.
| Crate | Latest | Migration shape | Best fit |
|---|---|---|---|
noyalib |
0.0 | Drop-in via features = ["compat-serde-yaml"] — zero call-site changes for typical users |
Codebases that want a serde_yml-shaped API on a modern, safe, pure-Rust backend |
serde-saphyr |
0.0 | Path rename for typed code; no Value DOM |
Typed-deserialise workloads (from_str::<MyStruct>) — the 95 % case. Doesn't fit codebases that hold dynamic Value trees in flight |
yaml-rust2 |
0.9 | Not serde-integrated — lower-level parser API | Users who were on the low-level serde_yml::libyml / loader surface (removed in this shim) and want to keep working at that level |
Decision guide
- You used
serde_yml::from_str/to_string/Value/Mapping/with::singleton_map*— picknoyalib. Thecompat-serde-yamlfeature was built for this exact migration: one-lineCargo.tomlchange, one search-replace on imports, done. - You only ever called
from_str::<MyStruct>and never touched theValuetype — pickserde-saphyr. Smaller surface thannoyalib, modern parser. - You were using
serde_yml::libyml::*orserde_yml::loader::Loader— pickyaml-rust2. Those surfaces are gone from this shim regardless;yaml-rust2is the natural Rust-native equivalent for low-level YAML processing.
One-minute migration paths
Side-by-side diff snippets for each destination. The full
function-mapping tables are in MIGRATION.md.
→ noyalib
-[dependencies]
-serde_yml = "0.0"
+[dependencies]
+noyalib = { version = "0.0.5", features = ["compat-serde-yaml"] }
-use serde_yml::{from_str, to_string, Value};
+use noyalib::compat::serde_yaml::{from_str, to_string, Value};
→ serde-saphyr (typed-only)
-[dependencies]
-serde_yml = "0.0"
+[dependencies]
+serde-saphyr = "0.0"
-use serde_yml::from_str;
+use serde_saphyr::from_str;
let cfg: MyConfig = from_str(yaml)?;
If your code holds a serde_yml::Value in flight, you cannot move
it directly to serde-saphyr — there is no equivalent Value
type. The two viable options are: (a) restructure to typed-only
deserialisation, or (b) pick noyalib instead.
→ yaml-rust2 (low-level)
-[dependencies]
-serde_yml = "0.0"
+[dependencies]
+yaml-rust2 = "0.9"
-use serde_yml::{from_str, Value};
-let v: Value = serde_yml::from_str(yaml)?;
+use yaml_rust2::YamlLoader;
+let docs = YamlLoader::load_from_str(yaml)?;
+let v = &docs[0];
yaml-rust2 returns a Yaml enum (its own AST), not a
serde::Deserialize value — bring your own typed-conversion code
or hand-write the read paths. This is the right choice when you
actually want the parser primitives.
What changed in 0.0.13
serde_yml 0.0.13 is a thin compatibility shim. The runtime
dependency list dropped from six crates (libyml, indexmap,
itoa, ryu, memchr, serde) to two (noyalib, serde) —
the C-FFI parser is no longer in the graph. Existing call sites
compile unchanged; the compiler emits a #[deprecated] warning at
each one so you can budget the migration.
The shim being backed by noyalib internally is an
implementation detail, not a recommendation to use noyalib
specifically. The
Maintained alternatives section above
covers the choice.
What still works in 0.0.13
The shim is wire-compatible with typical user code. Verified by
cargo test --all-targets + cargo run --example example +
cargo run --example migration:
| Surface | Status |
|---|---|
tests/shim.rs — typed round-trips, sub-module path imports, Error::location() |
9 / 9 pass |
examples/example.rs — aggregator running 17 sub-modules from serializer/, value/, with/ |
exits 0 |
examples/migration.rs — standalone shim demo |
exits 0 |
The full per-file inventory of retained / patched / removed
tests and examples is in MIGRATION.md § "Test and example
coverage in 0.0.13".
What was removed in 0.0.13
The deep internal modules that previous versions exposed leaked implementation details of the C-FFI parser. They are removed in this shim. The right replacement depends on which alternative you picked:
Removed from serde_yml |
What it was | Where it goes |
|---|---|---|
serde_yml::libyml::* |
Raw FFI bindings to C libyaml |
yaml-rust2 for low-level parsing; otherwise no equivalent (the pure-Rust replacements don't expose FFI) |
serde_yml::loader::Loader |
Low-level YAML event loader | yaml-rust2::YamlLoader; noyalib::load_all_as::<T> for typed |
serde_yml::de::{Event, Progress} |
Event enum + input cursor | Covered by yaml-rust2's parser API or noyalib's streaming Deserializer |
serde_yml::de::DocumentAnchor |
Anchor-resolution helper | noyalib and serde-saphyr resolve anchors transparently |
serde_yml::ser::{SerializerConfig, State} |
C-emitter configuration | noyalib::ser::Config |
serde_yml::modules::path::Path |
Error-path builder | noyalib::Error::location() / Error::path() |
serde_yml::value::Index |
Sealed trait for Value indexing |
noyalib::Value implements Index<&str> / Index<usize> natively |
The full table is in MIGRATION.md.
Behavioural notes
The shim is backed by noyalib's parser, which is intentionally
safer than the original serde_yml defaults. Two behaviours flow
through that you may need to handle:
-
Custom-tag scalars surface as
Value::Taggedrather than being silently coerced to the inner string. Exhaustive matches on the previous six-variantValueenum need either aValue::Tagged(_)arm or a call toValue::untag()/Value::untag_ref()before the match. -
YAML 1.2 strict booleans by default.
country: NOstays"NO"(the YAML 1.2 fix to the "Norway problem") instead of becomingfalse. The legacy boolean recognition was a YAML 1.1 resolver behaviour.
Migrations to serde-saphyr or yaml-rust2 will encounter the
same YAML 1.2 strictness (it is the spec-correct behaviour); only
noyalib exposes an explicit opt-back-in via
ParserConfig::version(YamlVersion::V1_1).
MSRV
serde_yml 0.0.13 requires Rust 1.85.0 (matching the
backend's MSRV). The previous releases required 1.56. Users who
cannot move past 1.56 should pin serde_yml = "=0.0.12" and plan
a migration window.
Documentation
| Document | Covers |
|---|---|
MIGRATION.md |
Find/replace tables per destination, full removed-surface mapping, test/example coverage triage, MSRV note |
noyalib — GitHub |
Drop-in destination via compat-serde-yaml feature |
serde-saphyr |
Typed-deserialise destination |
yaml-rust2 |
Low-level parser destination |
| docs.rs/serde_yml | API reference for this shim — every item carries the #[deprecated] banner |
License
Dual-licensed under Apache 2.0 or MIT, at your option.