TOML is a simple, ergonomic, and readable configuration format. Rust's package manager cargo uses TOML format. This article explains how to handle TOML format data in Rust.

In Rust, you can use the toml crate to parse and generate TOML format data. Combined with serde and toml, you can parse TOML strings into arbitrary Rust structs or serialize Rust structs into TOML format strings.

Adding Dependencies

To use toml and serde, add these dependencies by editing your Cargo.toml file:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
toml = "0.9"

Using the toml Crate

The toml crate provides a toml::Table struct representing TOML tables, which you can use to access and manipulate TOML data:

pub type Table = Map<String, Value>;

Here, Value represents toml::Value, an enum that represents TOML values:

pub enum Value {
    String(String),
    Integer(i64),
    Float(f64),
    Boolean(bool),
    Datetime(Datetime),
    Array(Array),
    Table(Table),
}

Here's a simple example demonstrating how to use toml::Table to manipulate TOML tables:

use toml::{Table, Value};

fn main() {
    let mut table = Table::new();

    // Insert key-value pairs
    table.insert("website".to_string(), Value::String("perfcode.com".to_string()));
    table.insert("total".to_string(), Value::Integer(100));
    table.insert("enabled".to_string(), Value::Boolean(true));

    // Get values
    let website = table.get("website").unwrap().as_str().unwrap();
    let total = table.get("total").unwrap().as_integer().unwrap();
    let enabled = table.get("enabled").unwrap().as_bool().unwrap();

    println!("website: {}, total: {}, enabled: {}", website, total, enabled);

    // Iterate through all key-value pairs
    for (key, value) in table.iter() {
        println!(" - {}: {:?}", key, value);
    }

    // Remove a key-value pair
    let removed_value = table.remove("total");
    println!("removed value: {:?}", removed_value);
}

Program Output

website: perfcode.com, total: 100, enabled: true
 - enabled: Boolean(true)
 - total: Integer(100)
 - website: String("perfcode.com")
removed value: Some(Integer(100))

Parsing TOML Documents

The simplest way to parse TOML documents is through the Table type:

use toml::Table;

fn main() {
    let toml_text = r#"
        a = "perfcode.com"
        b = true
        [others]
        c = 3
    "#;

    let value = toml_text.parse::<Table>().unwrap();
    
    println!("{}\n{}\n{}",
        value["a"].as_str().unwrap(),
        value["b"].as_bool().unwrap(),
        value["others"]["c"].as_integer().unwrap(),
    );
}

Program Output

perfcode.com
true
3

Using toml::from_str Function

The toml crate provides a toml::from_str function to parse (deserialize) TOML documents into toml::Table type. Here's a modified version of the previous example:

use toml::Table;

fn main() {
    let toml_text = r#"
        a = "perfcode.com"
        b = true
        [others]
        c = 3
    "#;

    let value: Table = toml::from_str(&toml_text).unwrap();

    println!("{}\n{}\n{}",
        value["a"].as_str().unwrap(),
        value["b"].as_bool().unwrap(),
        value["others"]["c"].as_integer().unwrap(),
    );
}

Using toml::to_string Function

You can use the toml::to_string function to serialize toml::Table type into a TOML string:

use toml::Table;

fn main() {
    let toml_text = r#"
        a = "perfcode.com"
    "#;

    let mut value: Table = toml::from_str(&toml_text).unwrap();
    value.insert("b".to_string(), toml::Value::String("www.perfcode.com".to_string()));

    let new_toml = toml::to_string(&value).unwrap();
    println!("{}", new_toml);
}

Program Output

a = "perfcode.com"
b = "www.perfcode.com"

Serializing and Deserializing Custom Structs

An example of TOML deserialization:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct Config {
    ip: String,
    port: u16,
    others: Others
}

#[derive(Deserialize, Serialize)]
struct Others {
    salt: String,
    use_whitelist: bool,
}

fn main() {
    let tom_text = r#"
        ip = "127.0.0.1"
        port = 80
        [others]
        salt = "xxxxxx"
        use_whitelist = false
    "#;

    let config: Config = toml::from_str(tom_text).unwrap();

    println!("ip: {}\nport: {}\nsalt: {}\nuse_whitelist: {}",
        config.ip,
        config.port,
        config.others.salt,
        config.others.use_whitelist
    );
}

Program Output

ip: 127.0.0.1
port: 80
salt: xxxxxx
use_whitelist: false

An example of TOML serialization:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct Config {
    ip: String,
    port: u16,
    others: Others
}

#[derive(Deserialize, Serialize)]
struct Others {
    salt: String,
    use_whitelist: bool,
}

fn main() {
    // Serialization
    let new_config = Config {
        ip: "0.0.0.0".to_string(),
        port: 80,
        others: Others {
            salt: "ssssssssss".to_string(),
            use_whitelist: true
        }
    };
    let new_toml = toml::to_string(&new_config).unwrap();
    
    println!("{}", new_toml);
}

Program Output

ip = "0.0.0.0"
port = 80

[others]
salt = "ssssssssss"
use_whitelist = true