Se hai aperto un progetto Rust, hai già incontrato TOML. Se hai toccato un pacchetto Python che usa pyproject.toml, l'hai usato anche tu. TOML — Tom's Obvious Minimal Language — è il formato di file di configurazione che continua silenziosamente a vincere tra gli sviluppatori stanchi delle mine whitespace di YAML e del rifiuto di JSON di consentire commenti. Creato da Tom Preston-Werner, cofondatore di GitHub, TOML è stato progettato attorno a un'idea: un formato di configurazione dovrebbe essere così ovvio che puoi leggerlo senza una specifica. Vediamo se mantiene quella promessa.

Cos'è Esattamente TOML?

TOML è un formato di file di configurazione con tre obiettivi di design espliciti, enunciati subito all'inizio della specifica ufficiale: dovrebbe essere ovvio da leggere, minimale in complessità, e mappare inequivocabilmente su una hash table (un dizionario/mappa nella maggior parte dei linguaggi). Quel terzo obiettivo è quello chiave — ogni file TOML valido ha esattamente un risultato di parsing corretto. Nessuna coercizione di tipo sorprendente, nessuna mine booleane stile YAML, nessuna ambiguità su se un valore è una stringa o un numero.

Il formato prende in prestito lo stile degli header di sezione dai vecchi file INI ma aggiunge tipi propri, array e tabelle annidate. Il risultato è qualcosa che sembra familiare a chiunque abbia modificato un file di configurazione prima, ma con abbastanza struttura che un parser può darti un vero modello dati tipizzato. La versione 1.0.0 della specifica è stata rilasciata nel gennaio 2021 dopo anni di raffinamento — puoi consultare la specifica TOML su GitHub se vuoi approfondire i casi limite.

Le Basi: Coppie Chiave-Valore e Commenti

Un file TOML è composto da coppie chiave-valore. Chiavi e valori sono separati da =, e i commenti iniziano con #. Semplice.

toml
# Cargo.toml — Rust package manifest
[package]
name = "image-resizer"
version = "0.4.2"
edition = "2021"
authors = ["Ada Lovelace <[email protected]>"]
description = "Fast image resizing with Lanczos3 resampling"
license = "MIT"
repository = "https://github.com/example/image-resizer"

# Integers, floats, booleans — all native types
max_threads = 8
quality_default = 0.85
verbose_logging = false

Nessuna virgoletta necessaria sulla maggior parte delle chiavi. I valori sono tipizzati — 8 è un intero, 0.85 è un float, false è un booleano. Nessuna supposizione, nessuna coercizione implicita basata sull'aspetto del valore. Questo è il TOML quotidiano che scriverai il 90% delle volte.

Tipi di Stringa: Basic, Literal e Multiline

TOML ha quattro tipi di stringa. Questo copre ogni caso reale in modo pulito:

toml
# Basic strings — double quotes, support escape sequences
greeting = "Hello, \nworld!"
path = "C:\\Users\\ada\\Documents"

# Literal strings — single quotes, no escape processing at all
regex_pattern = '\d{4}-\d{2}-\d{2}'
windows_path = 'C:\Users\ada'

# Multiline basic string — triple double quotes
sql_query = """
  SELECT user_id, email, created_at
  FROM users
  WHERE active = true
    AND created_at > '2024-01-01'
  ORDER BY created_at DESC
"""

# Multiline literal string — triple single quotes, no escapes
shell_script = '''
#!/bin/bash
echo "Deploying $APP_NAME to $ENV"
kubectl apply -f k8s/
'''

Il tipo di stringa literal ('virgolette singole') è quello che le persone dimenticano esista, ed è genuinamente utile — i pattern regex e i percorsi Windows sono molto più puliti senza il raddoppio degli escape. Scegli lo stile di virgolette che ti fa scrivere meno backslash.

Numeri, Booleani e Datetime

I tipi nativi di TOML coprono tutto ciò che metteresti effettivamente in un file di configurazione. In particolare, ha supporto datetime di prima classe — qualcosa che YAML tecnicamente ha ma gestisce in modo incoerente tra i parser.

toml
# Integers — underscores allowed as separators (like numeric literals in code)
max_connections = 1_000_000
port = 5432
hex_color = 0xFF6B6B      # hex prefix supported
octal_permissions = 0o755  # octal prefix supported

# Floats
pi = 3.14159265
compression_ratio = 1.5e-3
infinity_val = inf         # special values: inf, -inf, nan

# Booleans — lowercase only (not True, TRUE, yes, on)
ssl_enabled = true
dry_run = false

# Datetimes — RFC 3339 format
created_at = 2024-03-15T09:30:00Z
updated_at = 2024-03-15T14:22:10+05:30
log_date = 2024-03-15           # local date (no time)
backup_time = 03:00:00          # local time (no date)
I booleani TOML sono strettamente minuscoli. Solo true e false — non True, TRUE, yes, on, o nessuna delle altre varianti che YAML 1.1 accetta. Questo è intenzionale. Se hai bisogno di una stringa "true", mettila tra virgolette.

Tabelle: Gli Header di Sezione Stile INI

Le tabelle in TOML sono definite con la sintassi [header]. Tutto ciò che segue un header appartiene a quella tabella finché non appare il prossimo header. Questa è la caratteristica che rende TOML familiare — è essenzialmente file INI ma con tipi.

toml
[database]
host = "db.internal"
port = 5432
name = "app_production"
pool_size = 20

[database.credentials]
username = "app_user"
# Don't put real passwords here — use env vars or a secrets manager
password_env = "DB_PASSWORD"

[server]
host = "0.0.0.0"
port = 8080
workers = 4

[server.tls]
enabled = true
cert_file = "/etc/ssl/certs/app.crt"
key_file = "/etc/ssl/private/app.key"

Gli header puntati come [database.credentials] creano tabelle annidate. Il risultato analizzato è esattamente quello che ti aspetteresti: un oggetto database con un oggetto credentials annidato. Puoi anche scrivere tabelle inline per casi semplici — ne parleremo sotto.

Array e Array di Tabelle

Gli array in TOML usano parentesi quadre e possono estendersi su più righe. La caratteristica davvero distintiva di TOML è l'Array di Tabelle — definito con doppie parentesi [[header]]. Questa è la risposta di TOML a "come esprimo una lista di oggetti?" senza che sembri JSON.

toml
# Regular arrays — can be split across lines, trailing comma is fine
allowed_origins = [
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]

supported_formats = ["jpeg", "png", "webp", "avif"]
retry_delays_ms = [100, 250, 500, 1000, 2000]

# Array of Tables — [[double brackets]]
# Each [[servers]] header appends a new object to the servers array
[[servers]]
name = "web-01"
ip = "10.0.1.10"
role = "primary"
tags = ["web", "prod"]

[[servers]]
name = "web-02"
ip = "10.0.1.11"
role = "replica"
tags = ["web", "prod"]

[[servers]]
name = "db-01"
ip = "10.0.2.10"
role = "primary"
tags = ["database", "prod"]

La sintassi [[servers]] viene analizzata in un array di tre oggetti — equivalente a "servers": [{...}, {...}, {...}] in JSON. È verboso rispetto agli array JSON di oggetti, ma il vantaggio è la leggibilità quando ogni elemento ha molti campi. Puoi vedere questo pattern abbondantemente nei manifest Cargo.toml per la definizione di più target binari, esempi e voci bench.

Tabelle Inline: Compatte su Una Riga

Quando una tabella ha solo un paio di campi e non vuoi un intero header di sezione per essa, le tabelle inline ti permettono di scriverla su una singola riga:

toml
[build]
# Inline table — must stay on one line
target = { arch = "x86_64", os = "linux", libc = "musl" }

# Equivalent to writing:
# [build.target]
# arch = "x86_64"
# os = "linux"
# libc = "musl"

[feature_flags]
auth   = { enabled = true, rollout_pct = 100 }
search = { enabled = true, rollout_pct = 50 }
beta   = { enabled = false, rollout_pct = 0 }

Le tabelle inline devono restare su una singola riga e non possono essere estese in seguito con un [header]. Usale per piccoli gruppi di valori coesi — sono ottime per cose come coppie di coordinate, target di build, o semplici configurazioni di flag. Non usarle quando hai più di tre o quattro campi; a quel punto una tabella normale è più leggibile.

TOML nel Mondo Reale

TOML ha conquistato una nicchia forte negli ecosistemi Rust e Python, e sta guadagnando terreno altrove. Ecco dove lo incontrerai quotidianamente:

  • Rust — Cargo.toml: ogni progetto Rust ne ha uno. Definisce i metadati del pacchetto, le dipendenze, le funzionalità e i target di build. Il riferimento al manifest Cargo è la guida all'utilizzo reale di TOML più dettagliata che troverai.
  • Python — pyproject.toml: PEP 518 e PEP 621 hanno standardizzato TOML come formato dei metadati del progetto Python. Poetry, Hatch, PDM e setuptools lo leggono tutti.
  • Deno: il file di configurazione Deno supporta TOML oltre a JSON.
  • Hugo: il generatore di siti statici Hugo accetta TOML come formato di configurazione e front-matter — lo vedrai tra i delimitatori +++ in cima ai file Markdown.
  • uv: il veloce gestore di pacchetti Python di Astral usa pyproject.toml per tutta la configurazione, rendendo TOML lo standard de facto per i nuovi strumenti Python.

Se devi convertire una configurazione esistente tra formati, i convertitori TOML to JSON e JSON to TOML gestiscono la mappatura strutturale. Oppure usa il TOML Formatter per pulire la spaziatura incoerente in un file esistente, e il TOML Validator per individuare gli errori di sintassi prima che emergano in produzione.

Parsing TOML in Python

A partire da Python 3.11, la libreria standard include tomllib — nessuna dipendenza esterna necessaria. Per Python 3.9 e 3.10, il backport tomli ha un'API identica, quindi puoi passare tra di loro con un singolo alias di importazione.

python
import sys

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib  # pip install tomli

# tomllib only reads binary mode — open with "rb"
with open("pyproject.toml", "rb") as f:
    config = tomllib.load(f)

# Types match TOML exactly: str, int, float, bool, datetime, list, dict
project_name = config["project"]["name"]               # str
python_requires = config["project"]["requires-python"] # str
dependencies = config["project"]["dependencies"]        # list[str]

print(f"Project: {project_name}")
print(f"Requires Python: {python_requires}")
print(f"Dependencies ({len(dependencies)}):")
for dep in dependencies:
    print(f"  {dep}")

# Parse from a string with tomllib.loads()
raw = """
[server]
host = "localhost"
port = 8080
debug = true
"""
server_config = tomllib.loads(raw)
print(server_config["server"]["port"])   # 8080 (int, not "8080")

Nota che tomllib.load() richiede la modalità binaria ("rb"). Questo è intenzionale — TOML richiede la codifica UTF-8, e aprire in modalità binaria permette al parser di gestire il controllo della codifica stesso. È una piccola insidia che sorprende le persone la prima volta.

Parsing TOML in Rust

In Rust, il crate toml è la scelta standard. Si integra strettamente con serde, quindi puoi deserializzare direttamente nelle tue struct con un minimo di codice boilerplate:

rust
use serde::Deserialize;
use std::fs;

#[derive(Debug, Deserialize)]
struct AppConfig {
    server: ServerConfig,
    database: DatabaseConfig,
    feature_flags: FeatureFlags,
}

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

#[derive(Debug, Deserialize)]
struct DatabaseConfig {
    host: String,
    port: u16,
    name: String,
    pool_size: u32,
}

#[derive(Debug, Deserialize)]
struct FeatureFlags {
    enable_beta: bool,
    max_upload_mb: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let raw = fs::read_to_string("config.toml")?;
    let config: AppConfig = toml::from_str(&raw)?;

    println!("Server: {}:{}", config.server.host, config.server.port);
    println!("DB pool size: {}", config.database.pool_size);
    println!("Beta enabled: {}", config.feature_flags.enable_beta);

    Ok(())
}

Aggiungi toml = "0.8" e serde = { version = "1", features = ["derive"] } nelle tue [dependencies] in Cargo.toml e sei pronto. Le macro derive di serde gestiscono tutta la mappatura dei campi. Se i nomi dei campi della tua struct usano snake_case ma le chiavi TOML usano kebab-case, aggiungi #[serde(rename_all = "kebab-case")] a livello di struct e tutto si mappa automaticamente.

TOML vs YAML vs JSON — Quando Scegliere Quale

Questa domanda viene posta su ogni nuovo progetto. Ecco il resoconto onesto:

  • TOML: migliore per file di configurazione che gli esseri umani scrivono e mantengono, dove i valori tipizzati sono importanti e la nidificazione è superficiale-moderata. Punto di forza: configurazioni di app, manifest di build, impostazioni degli strumenti. Si rompe per la nidificazione profonda — 4+ livelli diventano scomodi con header di sezione ripetuti.
  • YAML: migliore quando scrivi dati strutturati con molte liste-di-oggetti (manifest Kubernetes, workflow GitHub Actions). Il supporto per stringhe multiriga è genuinamente migliore di TOML. Lo svantaggio: la sensibilità agli spazi bianchi e la coercizione dei tipi YAML 1.1 creano bug reali in pratica.
  • JSON: migliore per lo scambio di dati macchina-macchina, API, e quando hai bisogno del supporto toolchain più ampio possibile. Non ottimo per configurazioni mantenute dall'uomo — nessun commento, e l'escaping delle stringhe è tedioso.
Regola pratica rapida: se uno sviluppatore sta modificando il file manualmente in un editor di testo, TOML è di solito l'esperienza più piacevole. Se è generato da uno strumento o consumato da una dozzina di sistemi diversi in linguaggi diversi, l'universalità di JSON vince. YAML vive nel mezzo — ottimo per la configurazione DevOps che gli esseri umani scrivono ma gli strumenti elaborano pesantemente.

Un pyproject.toml Reale

Per mettere tutto insieme, ecco un realistico pyproject.toml per una libreria Python — il tipo che troveresti in un moderno progetto open-source. Nota come il formato porta molte informazioni strutturate rimanendo comunque facile da scansionare:

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "httpx-cache"
version = "1.2.0"
description = "Transparent HTTP caching layer for httpx"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
  { name = "Ada Lovelace", email = "[email protected]" },
]
keywords = ["http", "cache", "httpx", "async"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Intended Audience :: Developers",
  "License :: OSI Approved :: MIT License",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
]
dependencies = [
  "httpx>=0.25.0",
  "anyio>=4.0.0",
]

[project.optional-dependencies]
redis = ["redis>=5.0.0"]
dev = [
  "pytest>=7.4.0",
  "pytest-asyncio>=0.23.0",
  "coverage[toml]>=7.3.0",
  "ruff>=0.1.0",
  "mypy>=1.7.0",
]

[project.urls]
Homepage = "https://github.com/example/httpx-cache"
Changelog = "https://github.com/example/httpx-cache/blob/main/CHANGELOG.md"

[tool.ruff]
line-length = 100
target-version = "py39"

[tool.ruff.lint]
select = ["E", "F", "I", "UP"]

[tool.mypy]
strict = true
python_version = "3.9"

[tool.coverage.run]
source = ["httpx_cache"]
branch = true

[tool.coverage.report]
fail_under = 90

Questo è TOML reale che fa lavoro reale. Ogni sezione [tool.x] è uno spazio dei nomi separato per uno strumento diverso — ruff, mypy, coverage — che vivono tutti in un file senza interferire l'uno con l'altro. Non è necessaria nidificazione profonda, tutto è leggibile a colpo d'occhio.

Conclusione

TOML mantiene la sua promessa: leggibile, non ambiguo e tipizzato in modo pulito. Se stai avviando un nuovo progetto in Rust, Python o Go, TOML vale la pena essere il predefinito per i file di configurazione — specialmente i file che verranno committati nel controllo del codice sorgente e modificati da più persone. La sola mancanza di sensibilità agli spazi bianchi lo rende un sollievo rispetto a YAML. Per lavorare direttamente con file TOML nel tuo browser, il TOML Formatter e il TOML Validator gestiscono le attività più comuni. E se stai migrando un progetto esistente da JSON o hai bisogno di collegare TOML a una pipeline basata su JSON, i convertitori TOML to JSON e JSON to TOML ti coprono.