Thanks to visit codestin.com
Credit goes to lib.rs

4 releases

Uses new Rust 2024

0.2.2 Oct 11, 2025
0.2.1 Aug 11, 2025
0.2.0 Jun 26, 2025
0.1.0 Jun 19, 2025

#94 in Configuration

Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App

1,770 downloads per month
Used in lazycloud

MIT license

74KB
2K SLoC

serini Crates.io

A serde-based INI file parser for Rust that supports automatic serialization and deserialization of structs to INI format.

Features

  • 🚀 Automatic serialization - Convert Rust structs to INI format
  • 🔧 Type-safe deserialization - Parse INI files into strongly-typed Rust structs
  • 📁 Section support - Nested structs become INI sections automatically
  • 💭 Option handling - None values are serialized as commented lines if serialization is not skipped using #[serde(skip)] or similiar
  • 🛡️ Escape sequences - Properly handles special characters in values
  • 🏷️ Serde integration - Supports serde attributes like #[serde(rename = "...")]

Installation

Add this to your Cargo.toml:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serini = "0.2"

Quick Example

use serde::{Deserialize, Serialize};
use serini::{from_str, to_string};

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    name: String,
    port: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    debug: Option<usize>,
    database: Database,
}

#[derive(Debug, Serialize, Deserialize)]
struct Database {
    host: String,
    username: String,
    password: Option<String>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config {
        name: "My App".to_string(),
        port: 8080,
        debug: None,
        database: Database {
            host: "localhost".to_string(),
            username: "admin".to_string(),
            password: None,
        },
    };

    // Serialize to INI
    let ini_string = to_string(&config)?;
    println!("{}", ini_string);

    // Deserialize from INI
    let parsed: Config = from_str(&ini_string)?;
    assert_eq!(config, parsed);
    Ok(())
}

Output:

name = My App
port = 8080

[database]
host = localhost
username = admin
; password = 

Usage Guide

Basic Types

serini supports all basic Rust types:

#[derive(Serialize, Deserialize)]
struct Settings {
    // Integers
    max_connections: u32,      // max_connections = 100
    retry_count: i8,          // retry_count = 3
    
    // Floats
    timeout: f64,             // timeout = 30.5
    
    // Booleans
    debug_mode: bool,         // debug_mode = true
    
    // Strings
    server_name: String,      // server_name = Production Server
    
    // Options
    description: Option<String>, // ; description =  (when None)
}

Sections from Nested Structs

Nested structs automatically become INI sections:

#[derive(Serialize, Deserialize)]
struct App {
    version: String,
    
    server: ServerConfig,
    database: DbConfig,
}

#[derive(Serialize, Deserialize)]
struct ServerConfig {
    host: String,
    port: u16,
}

#[derive(Serialize, Deserialize)]
struct DbConfig {
    url: String,
    pool_size: u32,
}

Produces:

version = 1.0.0

[server]
host = 0.0.0.0
port = 8080

[database]
url = postgres://localhost/mydb
pool_size = 10

Self-Referential Structs

serini supports self-referential structs using Option<Box<T>>, allowing sections to override values from the root configuration:

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    speed: f32,
    movie: Option<Box<Config>>,
    anime: Option<Box<Config>>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Parse from INI
    let ini_str = r#"
speed = 1

[anime]
speed = 1.5

[movie]
speed = 2
"#;

    let config: Config = from_str(ini_str)?;
    
    println!("Default speed: {}", config.speed);
    if let Some(anime_config) = &config.anime {
        println!("Anime speed: {}", anime_config.speed);
    }
    if let Some(movie_config) = &config.movie {
        println!("Movie speed: {}", movie_config.speed);
    }

    // Serialize to INI
    let new_config = Config {
        speed: 1.0,
        anime: Some(Box::new(Config {
            speed: 1.5,
            anime: None,
            movie: None,
        })),
        movie: Some(Box::new(Config {
            speed: 2.0,
            anime: None,
            movie: None,
        })),
    };

    let ini_output = to_string(&new_config)?;
    println!("{}", ini_output);
    Ok(())
}

Output:

speed = 1

[anime]
speed = 1.5

[movie]
speed = 2

This pattern is useful for configuration files where different profiles or modes can override default settings.

Field Renaming

Use serde's rename attribute:

#[derive(Serialize, Deserialize)]
struct Config {
    #[serde(rename = "app-name")]
    app_name: String,
    
    #[serde(rename = "log-level")]
    log_level: String,
}

Escape Sequences

Special characters are automatically escaped:

Character Escaped
\ \\
newline \n
tab \t
" \"
; \;
# \#

Real-World Example

use serde::{Deserialize, Serialize};
use serini::{from_str, to_string};

#[derive(Debug, Serialize, Deserialize)]
struct ServerConfig {
    #[serde(rename = "app-name")]
    app_name: String,
    environment: String,
    
    http: HttpConfig,
    database: DatabaseConfig,
    redis: Option<RedisConfig>,
}

#[derive(Debug, Serialize, Deserialize)]
struct HttpConfig {
    host: String,
    port: u16,
    #[serde(rename = "request-timeout")]
    request_timeout: u64,
    #[serde(rename = "enable-tls")]
    enable_tls: bool,
}

#[derive(Debug, Serialize, Deserialize)]
struct DatabaseConfig {
    url: String,
    #[serde(rename = "max-connections")]
    max_connections: u32,
    #[serde(rename = "connection-timeout")]
    connection_timeout: u64,
}

#[derive(Debug, Serialize, Deserialize)]
struct RedisConfig {
    url: String,
    #[serde(rename = "connection-pool")]
    connection_pool: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ServerConfig {
        app_name: "my-awesome-app".to_string(),
        environment: "production".to_string(),
        
        http: HttpConfig {
            host: "0.0.0.0".to_string(),
            port: 443,
            request_timeout: 30,
            enable_tls: true,
        },
        
        database: DatabaseConfig {
            url: "postgres://user:pass@localhost/mydb".to_string(),
            max_connections: 100,
            connection_timeout: 5,
        },
        
        redis: None,
    };

    let ini = to_string(&config)?;
    println!("{}", ini);
    Ok(())
}

Output:

app-name = my-awesome-app
environment = production

[http]
host = 0.0.0.0
port = 443
request-timeout = 30
enable-tls = true

[database]
url = postgres://user:pass@localhost/mydb
max-connections = 100
connection-timeout = 5

; redis = 

API

// Serialize a value to INI string
pub fn to_string<T: Serialize>(value: &T) -> Result<String, Error>

// Deserialize from INI string
pub fn from_str<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T, Error>

Error Types

serini uses a custom error type with helpful error messages:

use serini::Error;

match result {
    Err(Error::InvalidValue { typ, value }) => {
        eprintln!("Invalid {} value: {}", typ, value);
    }
    Err(Error::UnsupportedFeature(feature)) => {
        eprintln!("Unsupported feature: {}", feature);
    }
    Err(e) => eprintln!("Error: {}", e),
    Ok(_) => {}
}

Limitations

The following types are not supported:

  • Sequences (Vec, arrays)
  • Tuples and tuple structs
  • Enums with variants
  • Maps (HashMap, BTreeMap)
  • Nested arrays or complex data structures

Why serini?

  • Simple - Minimal API with just two functions
  • Type-safe - Leverages Rust's type system and serde
  • Automatic - No manual parsing or writing required
  • Flexible - Supports Options, escaping, and field renaming
  • Fast - Zero-copy deserialization where possible

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Dependencies

~0.2–0.8MB
~18K SLoC