Jeśli otworzyłeś projekt Rust, znasz TOML. Jeśli dotknąłeś pakietu Python używającego pyproject.toml, też go używałeś. TOML — Tom's Obvious Minimal Language — to format pliku konfiguracyjnego, który cicho zdobywa coraz więcej deweloperów zmęczonych pułapkami białych znaków YAML i odmową komentarzy przez JSON. Stworzony przez Toma Preston-Wernera, współzałożyciela GitHub, TOML został zaprojektowany wokół jednej idei: format konfiguracyjny powinien być tak oczywisty, że możesz go czytać bez specyfikacji. Zobaczmy, czy spełnia tę obietnicę.

Czym dokładnie jest TOML?

TOML to format pliku konfiguracyjnego z trzema jawnymi celami projektowymi, wymienionymi na początku oficjalnej specyfikacji: powinien być oczywisty do czytania, minimalny w złożoności i mapować się jednoznacznie na tablicę haszującą (słownik/mapę w większości języków). Ten trzeci cel jest kluczowy — każdy poprawny plik TOML ma dokładnie jeden prawidłowy wynik parsowania. Brak zaskakującej koercji typów, brak pułapek logicznych w stylu YAML, brak niejednoznaczności co do tego, czy wartość jest ciągiem czy liczbą.

Format zapożycza styl nagłówków sekcji ze starych plików INI, ale dodaje do nich właściwe typy, tablice i zagnieżdżone tabele. Wynik to coś, co jest znajome dla każdego, kto kiedykolwiek edytował plik konfiguracyjny, ale z wystarczającą strukturą, że parser może dostarczyć prawdziwy typowany model danych. Wersja 1.0.0 specyfikacji została wydana w styczniu 2021 po latach dopracowywania — możesz przeglądać pełną specyfikację TOML na GitHub, jeśli chcesz zagłębić się w przypadki brzegowe.

Podstawy: pary klucz-wartość i komentarze

Plik TOML składa się z par klucz-wartość. Klucze i wartości są oddzielone =, a komentarze zaczynają się od #. Prosto.

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

Większość kluczy nie wymaga cudzysłowów. Wartości są typowane — 8 to liczba całkowita, 0.85 to liczba zmiennoprzecinkowa, false to wartość logiczna. Bez zgadywania, bez niejawnej koercji na podstawie wyglądu wartości. To codzienny TOML, który będziesz pisał przez 90% czasu.

Typy ciągów: podstawowe, literalne i wieloliniowe

TOML ma cztery typy ciągów. Obejmuje to każdy rzeczywisty przypadek:

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/
'''

Typ ciągu literalnego ('pojedyncze cudzysłowy') jest tym, o którym ludzie zapominają, że istnieje, i jest naprawdę użyteczny — wzorce regex i ścieżki Windows są znacznie czystsze bez podwajania backslashy. Wybierz styl cudzysłowów, który oznacza mniej backslashy.

Liczby, wartości logiczne i daty/godziny

Natywne typy TOML obejmują wszystko, co faktycznie wstawiłbyś do pliku konfiguracyjnego. Warto zaznaczyć, że ma wsparcie dla dat/godzin pierwszej klasy — coś, co YAML technicznie ma, ale obsługuje niespójnie między parserami.

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)
Wartości logiczne TOML są ściśle małymi literami. Tylko true i false — nie True, TRUE, yes, on ani żaden z innych wariantów, które YAML 1.1 akceptuje. To jest celowe. Jeśli potrzebujesz ciągu "true", umieść go w cudzysłowie.

Tabele: nagłówki sekcji w stylu INI

Tabele w TOML są definiowane składnią [nagłówek]. Wszystko poniżej nagłówka należy do tej tabeli, dopóki nie pojawi się następny nagłówek. To funkcja, która sprawia, że TOML wygląda znajomo — to zasadniczo pliki INI, ale z typami.

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"

Nagłówki z kropkami jak [database.credentials] tworzą zagnieżdżone tabele. Sparsowany wynik jest dokładnie tym, czego oczekujesz: obiekt database z zagnieżdżonym obiektem credentials. Możesz też pisać tabele inline dla prostych przypadków — więcej o tym poniżej.

Tablice i tablice tabel

Tablice w TOML używają nawiasów kwadratowych i mogą obejmować wiele linii. Naprawdę charakterystyczną funkcją TOML jest Array of Tables — zdefiniowana podwójnymi nawiasami [[nagłówek]]. To odpowiedź TOML na pytanie "jak wyrażam listę obiektów?" bez wyglądania jak 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"]

Składnia [[servers]] parsuje się do tablicy trzech obiektów — odpowiadającej "servers": [{...}, {...}, {...}] w JSON. Jest bardziej rozbudowana niż tablice obiektów JSON, ale zaleta to czytelność, gdy każdy element ma wiele pól. Możesz zobaczyć ten wzorzec w manifestach Cargo.toml do definiowania wielu celów binarnych, przykładów i wpisów bench.

Tabele inline: kompaktowe jednolinijkowce

Gdy tabela ma tylko kilka pól i nie chcesz dla niej całego nagłówka sekcji, tabele inline pozwalają napisać to w jednej linii:

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 }

Tabele inline muszą pozostać w jednej linii i nie mogą być później rozszerzane za pomocą [nagłówka]. Używaj ich do małych, spójnych grup wartości — są świetne do par współrzędnych, celów budowania lub prostych konfiguracji flag. Nie używaj ich, gdy masz więcej niż trzy lub cztery pola; w tym momencie zwykła tabela czyta się lepiej.

TOML w prawdziwym świecie

TOML wyrzeźbił sobie mocną niszę w ekosystemach Rust i Python i zyskuje popularność gdzie indziej. Oto gdzie spotkasz go na co dzień:

  • Rust — Cargo.toml: Każdy projekt Rust ma jeden. Definiuje metadane pakietu, zależności, funkcje i cele budowania. Referencja manifestu Cargo to najbardziej szczegółowy przewodnik użycia TOML w prawdziwym świecie, jaki znajdziesz.
  • Python — pyproject.toml: PEP 518 i PEP 621 standaryzowały TOML jako format metadanych projektu Python. Poetry, Hatch, PDM i setuptools wszystkie go czytają.
  • Deno: Plik konfiguracyjny Deno obsługuje TOML oprócz JSON.
  • Hugo: Generator stron statycznych Hugo akceptuje TOML jako format konfiguracyjny i front-matter — zobaczysz go między ogranicznikami +++ na początku plików Markdown.
  • uv: Szybki menedżer pakietów Python od Astral używa pyproject.toml do całej konfiguracji, czyniąc TOML de facto standardem dla nowych narzędzi Python.

Jeśli musisz przekonwertować istniejącą konfigurację między formatami, konwertery TOML do JSON i JSON do TOML obsługują strukturalne mapowanie za Ciebie. Lub użyj TOML Formatter do oczyszczenia niespójnych odstępów w istniejącym pliku i TOML Validator do wykrycia błędów składniowych przed pojawieniem się w produkcji.

Parsowanie TOML w Pythonie

Od Pythona 3.11 standardowa biblioteka zawiera tomllib — bez zewnętrznych zależności. Dla Pythona 3.9 i 3.10, backport tomli ma identyczne API, więc możesz przełączać się między nimi za pomocą jednego aliasu importu.

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")

Pamiętaj, że tomllib.load() wymaga trybu binarnego ("rb"). To jest celowe — TOML wymaga kodowania UTF-8, a otwieranie w trybie binarnym pozwala parserowi samemu obsługiwać sprawdzanie kodowania. To małe zaskoczenie, które łapie ludzi za pierwszym razem.

Parsowanie TOML w Rust

W Rust, crate toml jest standardowym wyborem. Integruje się ściśle z serde, więc możesz deserializować bezpośrednio do własnych struktur z minimalnym szablonowym kodem:

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(())
}

Dodaj toml = "0.8" i serde = { version = "1", features = ["derive"] } do swojego [dependencies] w Cargo.toml i gotowe. Makra derive serde obsługują całe mapowanie pól. Jeśli nazwy pól struktury używają snake_case, ale klucze TOML używają kebab-case, dodaj #[serde(rename_all = "kebab-case")] na poziomie struktury i wszystko mapuje się automatycznie.

TOML vs YAML vs JSON — kiedy wybrać który

To pytanie pojawia się przy każdym nowym projekcie. Oto uczciwy podział:

  • TOML: Najlepszy dla plików konfiguracyjnych, które ludzie piszą i utrzymują, gdzie typy wartości mają znaczenie i zagnieżdżenie jest płytkie do umiarkowanego. Punkt mocny: konfiguracje aplikacji, manifesty budowania, ustawienia narzędzi. Zawodzi przy głębokim zagnieżdżeniu — 4+ poziomy stają się niezręczne z powtarzającymi się nagłówkami sekcji.
  • YAML: Najlepszy, gdy piszesz dane strukturalne z dużą ilością list obiektów (manifesty Kubernetes, przepływy pracy GitHub Actions). Obsługa wieloliniowych ciągów jest naprawdę lepsza niż TOML. Wadą jest wrażliwość na białe znaki i koercja typów YAML 1.1, które tworzą prawdziwe błędy w praktyce.
  • JSON: Najlepszy dla wymiany danych między maszynami, API i gdy potrzebujesz najszerszego możliwego wsparcia narzędzi. Nieodpowiedni dla konfiguracji utrzymywanej przez człowieka — brak komentarzy, a escaping ciągów jest żmudny.
Prosta zasada: Jeśli deweloper edytuje plik ręcznie w edytorze tekstu, TOML jest zazwyczaj najbardziej przyjemnym doświadczeniem. Jeśli jest generowany przez narzędzie lub konsumowany przez tuzin różnych systemów w różnych językach, wygrywa uniwersalność JSON. YAML żyje pośrodku — świetny dla konfiguracji DevOps, którą ludzie piszą, ale narzędzia intensywnie przetwarzają.

Prawdziwy pyproject.toml

Aby podsumować wszystko, oto realistyczny pyproject.toml dla biblioteki Python — taki, jaki znalazłbyś we współczesnym projekcie open-source. Zwróć uwagę na to, jak format przenosi dużo ustrukturyzowanych informacji, pozostając łatwym do skanowania:

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

To prawdziwy TOML wykonujący prawdziwą pracę. Każda sekcja [tool.x] to oddzielna przestrzeń nazw dla innego narzędzia — ruff, mypy, coverage — wszystkie żyjące w jednym pliku bez wchodzenia sobie w drogę. Nie wymaga głębokiego zagnieżdżenia, wszystko jest czytelne na pierwszy rzut oka.

Podsumowanie

TOML spełnia swoją obietnicę: czytelny, jednoznaczny i czysto typowany. Jeśli zaczynasz nowy projekt w Rust, Pythonie lub Go, TOML warto domyślnie wybierać dla plików konfiguracyjnych — szczególnie plików, które będą commitowane do systemu kontroli wersji i edytowane przez wiele osób. Sam brak wrażliwości na białe znaki jest ulgą w porównaniu z YAML. Do pracy z plikami TOML bezpośrednio w przeglądarce, TOML Formatter i TOML Validator obsługują najczęstsze zadania. A jeśli migrujesz istniejący projekt z JSON lub musisz połączyć TOML z pipeline'em opartym na JSON, konwertery TOML do JSON i JSON do TOML mają Cię pod opieką.