Introduction to Working with YAML in Rust
YAML (YAML Ain't Markup Language) is one of the most popular formats for configuration files due to its readability and flexibility. In the Rust ecosystem, the serde_yaml crate is used for working with YAML, which is built on the powerful Serde serialization platform. This library allows you to easily convert Rust structures to and from YAML.
Installation and Setup
Add the dependencies to your Cargo.toml:
[dependencies]serde = { version = "1.0", features = ["derive"] }serde_yaml = "0.9"After updating the project, run cargo build. The library is ready to use.
Basic YAML Deserialization in Rust
Let's look at a simple example — reading a YAML string and converting it into a Rust structure.
Step 1: Define the Structure
use serde::Deserialize;
#[derive(Debug, Deserialize)]struct Config { host: String, port: u16, debug: bool,}Step 2: Parse YAML
use serde_yaml;
fn main() -> Result<(), serde_yaml::Error> { let yaml_str = r#" host: "127.0.0.1" port: 8080 debug: true "#;
let config: Config = serde_yaml::from_str(yaml_str)?; println!("{:?}", config); Ok(())}Output: Config { host: "127.0.0.1", port: 8080, debug: true }
Serializing Rust Structures to YAML
The reverse process — converting Rust data to a YAML string:
use serde::Serialize;
#[derive(Debug, Serialize)]struct Server { name: String, ip: String, ports: Vec<u16>,}
fn main() -> Result<(), serde_yaml::Error> { let server = Server { name: "web-server".into(), ip: "10.0.0.1".into(), ports: vec![80, 443, 22], };
let yaml = serde_yaml::to_string(&server)?; println!("{}", yaml); Ok(())}Result:
---name: web-serverip: 10.0.0.1ports: - 80 - 443 - 22Working with Nested Structures and Tags
YAML supports complex hierarchies. In Rust, this is represented by nested structures and enums:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]struct Database { #[serde(rename = "db")] database: DbConfig, pool_size: u32,}
#[derive(Debug, Serialize, Deserialize)]struct DbConfig { host: String, port: u16, user: String, password: String,}
fn main() -> Result<(), serde_yaml::Error> { let yaml = r#" db: host: "localhost" port: 5432 user: "admin" password: "secret" pool_size: 10 "#;
let db: Database = serde_yaml::from_str(yaml)?; println!("{:#?}", db); Ok(())}Note the #[serde(rename = "db")] attribute — it allows mapping a Rust field to a different name in YAML.
Error Handling and Optional Fields
Real YAML files often contain optional parameters. Use Option<T>:
#[derive(Debug, Deserialize)]struct AppConfig { name: String, version: String, #[serde(default)] features: Vec<String>, #[serde(default = "default_timeout")] timeout: u64,}
fn default_timeout() -> u64 { 30}The #[serde(default)] attribute assigns a default value (empty vector for Vec, None for Option). You can specify a generator function, as in the timeout example.
Reading YAML from a File
In practice, configurations are stored in files. Reading is straightforward:
use std::fs;use serde_yaml;
fn main() -> Result<(), Box<dyn std::error::Error>