Other Formats
Serde plugs into many wire formats beyond JSON - each crate implements the same Serialize/Deserialize traits.
Recipe
Quick-reference recipe card - copy-paste ready.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Config { host: String, port: u16 }
let toml_str = toml::to_string(&config)?;
let yaml_str = serde_yaml::to_string(&config)?;
let bytes = bincode::serde::encode_to_vec(&config, bincode::config::standard())?;When to reach for this: Pick the format that matches your consumer - human config files, compact RPC, or cross-language messaging.
Working Example
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
host: String,
port: u16,
#[serde(default)]
workers: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cfg = Config { host: "127.0.0.1".into(), port: 3000, workers: 4 };
let toml_text = toml::to_string(&cfg)?;
let from_toml: Config = toml::from_str(&toml_text)?;
let yaml_text = serde_yaml::to_string(&cfg)?;
let from_yaml: Config = serde_yaml::from_str(&yaml_text)?;
let bytes = bincode::serde::encode_to_vec(&cfg, bincode::config::standard())?;
let (from_bin, _len): (Config, usize) =
bincode::serde::decode_from_slice(&bytes, bincode::config::standard())?;
assert_eq!(from_toml, from_bin);
Ok(())
}What this demonstrates:
- Same struct serializes to TOML, YAML, and bincode without changes.
defaultattribute helps config files omit optional keys.- bincode produces compact binary for service-to-service storage.
Deep Dive
Format Guide
| Format | Crate | Typical use |
|---|---|---|
| TOML | toml | Cargo.toml, app config |
| YAML | serde_yaml | K8s manifests, CI configs |
| MessagePack | rmp-serde | Compact multilingual RPC |
| bincode | bincode | Rust-native cache blobs |
Rust Notes
// MessagePack
let mut buf = Vec::new();
rmp_serde::encode::write(&mut buf, &cfg)?;Gotchas
- YAML security - Arbitrary YAML can be complex to parse safely. Fix: Treat YAML as trusted config only; prefer TOML for apps.
- bincode not portable across languages - Binary layout is Rust-centric. Fix: Use JSON, Protobuf, or MessagePack for polyglot systems.
- Format-specific types -
toml::Valuevsserde_json::Valuediffer. Fix: Stick to shared structs at boundaries. - Enum representation differs - Tagged enum JSON shape != bincode layout. Fix: Test round-trip per format in CI.
- Large YAML aliases - Billion laughs attack. Fix: Limit loader depth/size in untrusted contexts.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| JSON | Universal APIs | Human-edited config with comments |
| Protobuf | gRPC contracts | Quick CLI config |
| Postcard | no_std embedded | Desktop server defaults |
FAQs
TOML vs JSON for config?
TOML supports comments and is friendlier for ops-edited files.
MessagePack vs bincode?
MessagePack for non-Rust peers; bincode for Rust-only caches.
Can one struct serve all formats?
Yes if you avoid format-specific attributes conflicting - test each format.
YAML in production?
Acceptable for trusted deploy configs; never for untrusted user uploads.
Version bincode blobs?
Prefix schema version byte or use explicit migration on read.
serde_with crate?
Extra helpers for durations, timestamps across formats.
CSV?
serde + csv crate for tabular data, separate from this list.
Avro?
Schema registry stacks use apache-avro with different codegen model.
Feature flags?
Optional deps per format crate to keep binaries small.
Benchmark formats?
bincode/MessagePack win size; JSON wins interoperability.
Related
- serde Basics - Trait fundamentals
- JSON with serde_json - Default wire format
- Customizing Serialization - Attributes
- serde Best Practices - Schema stability
- Configuration and Secrets - Prod config
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.