Se você já abriu um projeto Rust, conheceu o TOML. Se já mexeu com um pacote Python que usa pyproject.toml, você já o usou. TOML — Tom's Obvious Minimal Language — é o formato de arquivo de configuração que silenciosamente continua conquistando desenvolvedores cansados das armadilhas de espaços em branco do YAML e da recusa do JSON em permitir comentários. Criado por Tom Preston-Werner, cofundador do GitHub, o TOML foi projetado em torno de uma ideia: um formato de configuração deve ser tão óbvio que você possa lê-lo sem precisar de uma especificação. Vamos ver se ele cumpre essa promessa.

O Que Exatamente É o TOML?

TOML é um formato de arquivo de configuração com três objetivos de design explícitos, declarados logo no topo da especificação oficial: deve ser óbvio para ler, mínimo em complexidade e mapeado inequivocamente para uma tabela de hash (um dicionário/mapa na maioria das linguagens). Esse terceiro objetivo é o crucial — todo arquivo TOML válido tem exatamente um resultado de análise correto. Sem coerção de tipos surpreendente, sem armadilhas de booleanos estilo YAML, sem ambiguidade sobre se um valor é uma string ou um número.

O formato empresta o estilo de cabeçalho de seção dos antigos arquivos INI, mas adiciona tipos adequados, arrays e tabelas aninhadas em cima. O resultado é algo que parece familiar para qualquer pessoa que já editou um arquivo de configuração antes, mas com estrutura suficiente para que um analisador possa fornecer um modelo de dados tipado real. A versão 1.0.0 da especificação foi lançada em janeiro de 2021 após anos de refinamento — você pode navegar pela especificação TOML completa no GitHub se quiser se aprofundar nos casos extremos.

O Básico: Pares Chave-Valor e Comentários

Um arquivo TOML é composto por pares chave-valor. Chaves e valores são separados por =, e os comentários começam com #. Simples.

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

Não são necessárias aspas na maioria das chaves. Os valores são tipados — 8 é um inteiro, 0.85 é um ponto flutuante, false é um booleano. Sem adivinhação, sem coerção implícita baseada na aparência do valor. Este é o TOML cotidiano que você escreverá 90% do tempo.

Tipos de String: Básica, Literal e Multilinha

TOML tem quatro tipos de string. Isso cobre todos os casos do mundo real de forma limpa:

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

O tipo de string literal ('aspas simples') é aquele que as pessoas esquecem que existe, e é genuinamente útil — padrões de regex e caminhos do Windows são muito mais limpos sem o dobramento de escape. Escolha o estilo de aspas que significa que você escreve menos barras invertidas.

Números, Booleanos e Datas/Horas

Os tipos nativos do TOML cobrem tudo que você realmente colocaria em um arquivo de configuração. Notavelmente, ele tem suporte de primeira classe para data/hora — algo que o YAML tecnicamente tem mas trata inconsistentemente entre analisadores.

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)
Os booleanos TOML são estritamente em minúsculas. Apenas true e false — não True, TRUE, yes, on, ou qualquer outra variante que o YAML 1.1 aceita. Isso é intencional. Se você precisa de uma string "true", use aspas.

Tabelas: Os Cabeçalhos de Seção no Estilo INI

As tabelas em TOML são definidas com a sintaxe [cabeçalho]. Tudo abaixo de um cabeçalho pertence àquela tabela até o próximo cabeçalho aparecer. Este é o recurso que faz o TOML parecer familiar — são essencialmente arquivos INI, mas com tipos.

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"

Cabeçalhos com pontos como [database.credentials] criam tabelas aninhadas. O resultado analisado é exatamente o que você esperaria: um objeto database com um objeto aninhado credentials. Você também pode escrever tabelas inline para casos simples — mais sobre isso abaixo.

Arrays e Array de Tabelas

Arrays em TOML usam colchetes e podem abranger várias linhas. O recurso realmente distintivo do TOML é o Array de Tabelas — definido com colchetes duplos [[cabeçalho]]. Essa é a resposta do TOML para "como expresso uma lista de objetos?" sem parecer 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"]

Essa sintaxe [[servers]] é analisada em um array de três objetos — equivalente a "servers": [{...}, {...}, {...}] em JSON. É mais verboso do que arrays de objetos JSON, mas a vantagem é a legibilidade quando cada item tem muitos campos. Você pode ver esse padrão muito em manifestos Cargo.toml para definir múltiplos alvos binários, exemplos e entradas de benchmark.

Tabelas Inline: Uma Linha Compacta

Quando uma tabela tem apenas alguns campos e você não quer um cabeçalho de seção inteiro para ela, as tabelas inline permitem escrevê-la em uma única linha:

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 }

Tabelas inline devem ficar em uma única linha e não podem ser estendidas posteriormente com um [cabeçalho]. Use-as para pequenos grupos de valores coesos — são ótimas para coisas como pares de coordenadas, alvos de compilação ou configurações simples de flags. Não as use quando você tem mais de três ou quatro campos; nesse ponto uma tabela regular é mais legível.

TOML no Mundo Real

O TOML conquistou um nicho forte nos ecossistemas Rust e Python, e está ganhando espaço em outros lugares. Aqui está onde você o encontrará no dia a dia:

  • Rust — Cargo.toml: Todo projeto Rust tem um. Define metadados do pacote, dependências, recursos e alvos de compilação. A referência do manifesto Cargo é o guia de uso TOML mais detalhado do mundo real que você encontrará.
  • Python — pyproject.toml: A PEP 518 e a PEP 621 padronizaram o TOML como formato de metadados do projeto Python. Poetry, Hatch, PDM e setuptools todos o leem.
  • Deno: O arquivo de configuração Deno suporta TOML além de JSON.
  • Hugo: O gerador de sites estáticos Hugo aceita TOML como formato de configuração e front-matter — você o verá entre delimitadores +++ no topo de arquivos Markdown.
  • uv: O gerenciador de pacotes Python rápido da Astral usa pyproject.toml para toda configuração, tornando o TOML o padrão de fato para as novas ferramentas Python.

Se você precisa converter uma configuração existente entre formatos, os conversores TOML para JSON e JSON para TOML lidam com o mapeamento estrutural para você. Ou use o Formatador TOML para limpar espaçamento inconsistente em um arquivo existente, e o Validador TOML para detectar erros de sintaxe antes que apareçam em produção.

Analisando TOML em Python

Desde o Python 3.11, a biblioteca padrão inclui tomllib — sem dependência externa necessária. Para Python 3.9 e 3.10, o backport tomli tem uma API idêntica, então você pode alternar entre eles com um único alias de importação.

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

Observe que tomllib.load() requer modo binário ("rb"). Isso é intencional — o TOML requer codificação UTF-8, e abrir em modo binário permite que o analisador lide com a verificação de codificação por conta própria. É uma pequena armadilha que pega as pessoas na primeira vez.

Analisando TOML em Rust

Em Rust, o crate toml é a escolha padrão. Integra-se estreitamente com serde, então você pode desserializar diretamente em suas próprias structs com boilerplate mínimo:

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

Adicione toml = "0.8" e serde = { version = "1", features = ["derive"] } às suas [dependencies] no Cargo.toml e está pronto. As macros de derivação serde cuidam de todo o mapeamento de campos. Se os nomes dos campos da sua struct usam snake_case mas as chaves TOML usam kebab-case, adicione #[serde(rename_all = "kebab-case")] no nível da struct e tudo mapeia automaticamente.

TOML vs YAML vs JSON — Quando Escolher Qual

Esta pergunta aparece em todo novo projeto. Aqui está a análise honesta:

  • TOML: Melhor para arquivos de configuração que humanos escrevem e mantêm, onde valores tipados importam e o aninhamento é raso a moderado. Ponto ideal: configs de app, manifestos de build, configurações de ferramentas. Fica desajeitado com aninhamento profundo — 4+ níveis torna-se incômodo com cabeçalhos de seção repetidos.
  • YAML: Melhor quando você está escrevendo dados estruturados com muita lista-de-objetos (manifestos Kubernetes, workflows GitHub Actions). O suporte a strings multilinha é genuinamente melhor que o do TOML. A desvantagem: a sensibilidade a espaços em branco e a coerção de tipos do YAML 1.1 criam bugs reais na prática.
  • JSON: Melhor para troca de dados máquina a máquina, APIs e quando você precisa do suporte de cadeia de ferramentas mais amplo possível. Não é ótimo para configuração mantida por humanos — sem comentários e o escape de strings é tedioso.
Regra geral rápida: Se um desenvolvedor está editando o arquivo manualmente em um editor de texto, o TOML é geralmente a experiência mais agradável. Se é gerado por uma ferramenta ou consumido por uma dúzia de sistemas diferentes em linguagens diferentes, a universalidade do JSON vence. YAML fica no meio — ótimo para config DevOps que humanos escrevem mas ferramentas processam muito.

Um pyproject.toml Real

Para juntar tudo, aqui está um pyproject.toml realista para uma biblioteca Python — o tipo que você encontraria em um projeto de código aberto moderno. Observe como o formato carrega muitas informações estruturadas enquanto ainda é fácil de escanear:

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

Este é o TOML real fazendo trabalho real. Cada seção [tool.x] é um namespace separado para uma ferramenta diferente — ruff, mypy, coverage — todos vivendo em um arquivo sem pisar um no outro. Sem aninhamento profundo necessário, tudo é legível de relance.

Conclusão

O TOML cumpre sua promessa: legível, inequívoco e claramente tipado. Se você está iniciando um novo projeto em Rust, Python ou Go, o TOML vale a pena como padrão para arquivos de configuração — especialmente arquivos que serão confirmados no controle de versão e editados por várias pessoas. A ausência de sensibilidade a espaços em branco por si só é um alívio em comparação com o YAML. Para trabalhar com arquivos TOML diretamente no seu navegador, o Formatador TOML e o Validador TOML cuidam das tarefas mais comuns. E se você está migrando um projeto existente do JSON ou precisa conectar TOML a um pipeline baseado em JSON, os conversores TOML para JSON e JSON para TOML têm você coberto.