rust-plist

A Rust library for reading and writing Apple property list (plist) files in XML, binary, and ASCII formats, with full Serde support.

Library
Cargo
v1.10.0
91stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
53/100Fair
Development Activity44
Maintenance44
Community52
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture82
Code Quality88
Innovation58
Learning Curve85

plist is a Rust crate for working with Apple’s property list format, the XML- and binary-encoded structured data files used throughout macOS, iOS, and Apple’s development toolchains (Info.plist, entitlements, preference files, and more). It supports reading and writing all three plist encodings — XML, binary, and Apple’s ASCII/OpenStep format — through a single unified Value type that mirrors the format’s native array, dictionary, boolean, data, date, real, integer, string, and UID variants.

Beyond the untyped Value API, the crate ships an optional serde integration (enabled by default) that lets consumers derive Serialize/Deserialize on their own Rust structs and read or write plists directly into and out of typed data, avoiding manual dictionary traversal. It also provides plist! and plist_dict! macros for constructing values inline, and dedicated readers/writers for streaming large plists without loading the entire structure into memory at once.

What You Get

  • A Value enum covering every plist primitive (array, dictionary, boolean, data, date, real, integer, string, UID) with format-agnostic read/write methods
  • Automatic format detection when reading — Value::from_file/from_reader figure out whether a plist is XML, binary, or ASCII encoded
  • Optional Serde integration for deriving typed structs directly from plist data instead of manually walking a Dictionary
  • Dedicated binary, XML, and ASCII readers/writers under the stream module for lower-level or streaming access
  • plist! and plist_dict! macros for building Value trees inline in Rust code
  • A Dictionary type (ordered, IndexMap-backed) that preserves key insertion order, matching how real plist files are structured

Common Use Cases

  • Parsing an app’s Info.plist to read bundle identifiers, version strings, and permission usage descriptions during macOS/iOS build tooling
  • Reading and validating iOS/macOS entitlements or provisioning profile plists in CI or code-signing pipelines
  • Serializing Rust configuration structs to XML or binary plist for tools that interoperate with Apple’s ecosystem (defaults, launchd jobs, mobileconfig profiles)
  • Building cross-platform Rust utilities that need to inspect or generate macOS preference files without shelling out to plutil or defaults

Under The Hood

Architecture The crate is organized around a single format-agnostic Value enum in src/value.rs that all three on-disk plist encodings ultimately read into and write from. Format-specific parsing and serialization live under src/stream/ (ascii_reader.rs, binary_reader.rs, binary_writer.rs, xml_reader.rs, xml_writer.rs), each implementing a shared streaming Event-based reader/writer interface defined in stream/mod.rs; Value::from_reader auto-detects encoding and dispatches to the right reader, while Value::from_reader_xml/from_reader_ascii bypass detection for callers who already know the format. Serde support is layered on top in de.rs/ser.rs, translating the Event stream into/from serde::Deserialize/Serialize calls rather than requiring a second parse pass, so typed and untyped access share the same underlying format code. A dedicated error.rs centralizes all failure modes (per-format syntax errors, unexpected event types, I/O errors) into one Error type carrying an optional file position, giving callers precise diagnostics regardless of which encoding tripped the error.

Tech Stack The crate targets Rust edition 2021 with an MSRV of 1.88, and keeps its dependency surface deliberately small: quick-xml 0.41 handles low-level XML tokenizing for the XML reader/writer, base64 0.22 encodes/decodes the Data variant, time 0.3 (with parsing/formatting features) backs the Date type, indexmap 2.1 gives Dictionary its order-preserving map semantics, and serde 1.0 is an optional dependency gated behind the (default-on) serde feature. There is no runtime, no async, and no I/O abstraction beyond std::io::{Read, Write, Seek} — the crate is a pure, synchronous parsing/serialization library intended to be linked into other tools.

Code Quality The crate enforces #![deny(warnings)] and denies broken rustdoc intra-doc links at the crate root, signaling a low tolerance for drift. Testing is extensive: serde_tests.rs alone runs to over a thousand lines covering typed round-trips, and tests/ adds fixture-driven tests against real .plist files (tests/data/), trybuild-based UI tests asserting macro compile errors (tests/ui/), and macrotest expansion tests (tests/expand/) that check the exact macro-expanded output of plist!/plist_dict!. A fuzz/ directory with its own Cargo.toml indicates the binary and XML readers are fuzz-tested against malformed input, which matters for a crate that parses untrusted files. Errors are modeled as an explicit typed Error/ErrorKind rather than panics or string errors, and a CHANGELOG following Keep a Changelog conventions tracks every release.

API Design The public surface is small and consistent: Value::from_file/from_reader pair with to_file_xml/to_file_binary/to_writer_xml/to_writer_binary for the untyped path, while from_file/from_bytes/from_reader and to_file_xml/to_file_binary (Serde variants) mirror the same shape for typed structs — so learning one half of the API largely teaches the other. The Dictionary type exposes a familiar map-like interface (get, iteration, insertion order) rather than inventing new vocabulary, and the crate’s own doc comments include runnable examples for both the Value and Serde entry points. Advanced/streaming APIs are intentionally hidden behind the enable_unstable_features_that_may_break_with_minor_version_bumps feature flag, keeping the default public API stable across minor versions while still giving power users an escape hatch — a deliberate, well-documented trade-off between ergonomics and flexibility.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search