Working with YAML in Rust: serde_yaml library

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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  - 22


Working 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>

Recommendations