Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
167: Serializing to TOML and YAML
A few years ago, I was working on a project where we used a JSON file for the application's main configuration. It seemed fine at first, but we quickly hit a wall. A DevOps engineer tried to add a new environment variable to the config, missed a single comma on the line above, and accidentally brought down the entire staging environment because the parser choked. We spent an hour debugging a syntax error that should have been trivial. That was the day I realized that while JSON is king for machine-to-machine communication, it's a nightmare for humans to edit. We migrated our config to TOML and our deployment manifests to YAML immediately.
Since you've already mastered the basics of serde, you know that the real power of the ecosystem is that the serialization logic is decoupled from the data format. To move from JSON to TOML or YAML, you don't change your structs; you just change the crate that handles the string conversion. It's a seamless swap.
Structuring Configuration with TOML
TOML (Tom's Obvious, Minimal Language) is essentially the gold standard for Rust configuration—after all, it's what Cargo.toml uses. It's designed to be mapped directly to hash tables, which makes it a perfect fit for Rust structs. When you're building a CLI tool or a long-running service, I highly recommend TOML for the settings file because it handles key-value pairs and sections much more cleanly than JSON.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct ServerConfig {
host: String,
port: u16,
timeout: Option<u32>,
[serde(rename = "database_settings")]
db: DatabaseConfig,
}
#[derive(Serialize, Deserialize, Debug)]
struct DatabaseConfig {
connection_limit: u32,
retry_on_failure: bool,
}
fn main() {
let config = ServerConfig {
host: "127.0.0.1".to_string(),
port: 8080,
timeout: Some(30),
db: DatabaseConfig {
connection_limit: 100,
retry_on_failure: true,
},
};
// Serializing to TOML string
let toml_string = toml::to_string(&config).expect("Failed to serialize to TOML");
println!("TOML Output:\n{}", toml_string);
}
Notice how the [serde(rename = "...")] attribute allows us to keep our Rust field names idiomatic (using db) while keeping the config file descriptive for the user. I've found that this prevents a lot of friction between the developers and the people actually managing the production config files.
Handling Complex Data with YAML
While TOML is great for simple configs, it gets clunky when you have deeply nested lists or complex hierarchies. That's where YAML comes in. You'll see YAML everywhere in the Kubernetes and CI/CD world because it handles nesting through indentation rather than explicit section headers. It's a bit more "loose" than TOML, but for deployment manifests or complex data definitions, it's far more readable.
To use it, you'll pull in the serde_yaml crate. The API is almost identical to the TOML crate, which is the beauty of the serde trait system. You just swap the function call.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Deployment {
app_name: String,
replicas: u32,
env_vars: Vec<EnvVar>,
}
#[derive(Serialize, Deserialize, Debug)]
struct EnvVar {
key: String,
value: String,
}
fn main() {
let deploy = Deployment {
app_name: "auth-service".to_string(),
replicas: 3,
env_vars: vec![
EnvVar { key: "LOG_LEVEL".into(), value: "debug".into() },
EnvVar { key: "DB_URL".into(), value: "postgres://...".into() },
],
};
let yaml_string = serde_yaml::to_string(&deploy).expect("Failed to serialize to YAML");
println!("YAML Output:\n{}", yaml_string);
}
One thing to keep in mind: YAML's flexibility is a double-edged sword. It has some strange edge cases (like the "Norway Problem" where the country code NO can be parsed as a boolean false). For most application configs, you won't hit this, but if you're dealing with arbitrary user-provided strings, just stay mindful of how YAML interprets types.
📋 Practical Task
Build a Multi-Format Config Porter
You need to create a utility that can take a system configuration struct and export it to both TOML and YAML formats simultaneously. This is common when you need to provide a "default" config file for users (TOML) but also need to generate a deployment manifest for a pipeline (YAML).
Requirements:
- Define a
SystemSettingsstruct that contains:version(String)features(AVec<String>)- A nested
NetworkSettingsstruct containingip(String) andport(u16).
- Implement a function
export_settings(settings: &SystemSettings)that prints the serialized TOML and YAML versions of the data to the console, clearly labeled. - Ensure you use
serdederive macros forSerializeandDeserialize. - Handle the potential errors from
toml::to_stringandserde_yaml::to_stringusingexpectorunwrapfor this exercise.
There are no comments for now.