Abre cualquier proyecto de Rust y encontrarás un Cargo.toml. Clona un repositorio
de GitHub y encontrarás una carpeta .github/workflows/ llena de YAML. Ambos formatos
hacen el mismo trabajo superficial — almacenar configuración estructurada que los humanos editan —
pero hacen compromisos muy diferentes. Si alguna vez el YAML ha alterado silenciosamente un valor,
o te has preguntado por qué Rust eligió TOML en lugar de YAML para su manifiesto de paquetes,
este es el artículo para ti.
La diferencia fundamental: explícito vs implícito
La división fundamental entre TOML y YAML trata sobre cuánto puede adivinar el analizador.
TOML es explícito: cada valor tiene
un tipo inequívoco. Las cadenas siempre van entre comillas. Los booleanos son exactamente
true o false. Los datetimes son valores de primera clase con su propia
sintaxis. No hay coerción de tipos implícita — el analizador no intenta ser listo.
YAML se inclina en la otra
dirección. Intenta ser conveniente: no necesitas comillas alrededor de la mayoría de las cadenas, y
el analizador infiere los tipos a partir de la apariencia del valor. Esa inferencia es lo que pilla
a la gente. La especificación YAML 1.1 (aún usada por muchas herramientas) trata yes,
no, on, off, true y false como
booleanos. Interpreta cadenas de versión sin comillas como 1.0 como flotantes. Y luego
está el Problema de Noruega.
Las famosas trampas de YAML
El Problema de Noruega se convirtió en algo así como un meme en los círculos DevOps. En
YAML 1.1, el código de país NO se analiza como el booleano false. Así que
una configuración que mapea códigos ISO de países a ajustes convertiría silenciosamente la entrada
de Noruega en un booleano. La
especificación YAML 1.2
lo corrigió, pero muchos analizadores ampliamente usados — incluido el modo predeterminado de PyYAML
hasta hace poco — aún apuntan a YAML 1.1. Verifica qué versión de especificación implementa
realmente tu toolchain antes de confiar en él.
NO,
Yes, on y off sin comillas se convierten todos en booleanos.
La solución es simple — poner las cadenas entre comillas — pero el problema es que es silencioso.
Tu configuración carga sin errores y obtienes un booleano donde esperabas una cadena.# YAML 1.1 implicit type coercion — all of these silently become booleans:
countries:
norway: NO # → false ← The Norway Problem
sweden: SE # → "SE" (fine, not in the boolean list)
enabled: yes # → true
disabled: no # → false
feature_flag: on # → true
another: off # → false
# Version strings can become numbers:
python_version: 3.10 # → float 3.1 (trailing zero dropped)
api_version: 1.0 # → float 1.0
# Safe: quote anything that could be ambiguous
python_version: "3.10"
country: "NO"
enabled: "yes"- Literales octales. En YAML 1.1,
010se analiza como8(octal), no10. Esto importa para valores de permisos de archivo como0755. - Tabulación vs espacio. YAML prohíbe caracteres de tabulación para la sangría. Pega código de un editor configurado para usar tabulaciones y obtendrás un error de análisis críptico — o peor, un desalineamiento silencioso.
- Nulos implícitos. Una clave sin valor se convierte en
null. Fácil de crear accidentalmente al editar a mano. - Sensibilidad a la sangría. Un espacio extra y un valor se desplaza silenciosamente de un padre a otro. Sin error, solo datos incorrectos.
TOML: cómo se ven realmente los tipos explícitos
El sistema de tipos de TOML está detallado en la
especificación TOML v1.0.
Las cadenas deben ir entre comillas (simples o dobles). Los booleanos son true o
false y nada más. Los enteros son enteros. Los flotantes son flotantes. Y los datetimes
— algo que ni JSON ni YAML tienen como tipo nativo — son valores de primera clase en TOML.
# TOML types are always unambiguous
name = "my-app" # string — must be quoted
version = "1.0.0" # string — quotes make it clear this is not a float
port = 8080 # integer
debug = false # boolean — only true/false, nothing else
threshold = 0.95 # float
# Datetime is a first-class type — no string parsing needed
created_at = 2024-01-15T09:30:00Z
build_date = 2024-03-20
# Arrays
allowed_hosts = ["localhost", "staging.example.com", "api.example.com"]
# Inline tables
[database]
host = "postgres.internal"
port = 5432
name = "payments_prod"
pool_size = 20El código de país NO es simplemente una cadena en TOML porque no está entre
comillas, pero TOML no infiere tipos a partir de valores similares a cadenas — los valores sin comillas
siguen reglas sintácticas estrictas. Para ser una cadena, necesita comillas. TOML simplemente no tiene
el mecanismo de coerción implícita que causa las trampas de YAML.
Lado a lado: una configuración de pipeline CI
Aquí hay un workflow de GitHub Actions — este es el territorio natural de YAML. El formato encaja naturalmente porque GitHub Actions espera YAML, el anidamiento basado en sangría refleja la estructura lógica, y los comentarios son esenciales para explicar configuraciones de pasos no obvias.
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- 'v*'
jobs:
build-and-publish:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build --if-present
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Ahora aquí está el manifiesto de proyecto equivalente en TOML — aquí es donde TOML brilla. Compara una estructura real de Cargo.toml con cómo se vería la misma configuración en YAML:
# Cargo.toml — Rust package manifest
[package]
name = "payments-service"
version = "2.4.1"
edition = "2021"
description = "Payment processing microservice"
license = "MIT"
authors = ["Alice Chen <[email protected]>"]
[dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }
tracing = "0.1"
anyhow = "1.0"
[dev-dependencies]
tokio-test = "0.4"
wiremock = "0.6"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1Las debilidades de TOML: array de tablas
TOML no está exento de asperezas. La sintaxis para los arrays de tablas —
[[double brackets]] — es una de las cosas más confusas de la especificación. Es la
forma de expresar lo que JSON escribiría como un array de objetos, y al principio se lee de manera
extraña.
# TOML array of tables — [[double brackets]] creates array entries
[[server]]
host = "web-01.example.com"
port = 443
region = "us-east-1"
[[server]]
host = "web-02.example.com"
port = 443
region = "us-west-2"
[[server]]
host = "web-03.example.com"
port = 443
region = "eu-west-1"
# The above is equivalent to this JSON:
# { "server": [
# { "host": "web-01.example.com", "port": 443, "region": "us-east-1" },
# { "host": "web-02.example.com", "port": 443, "region": "us-west-2" },
# { "host": "web-03.example.com", "port": 443, "region": "eu-west-1" }
# ]}En YAML la misma estructura se lee más naturalmente — solo una lista con propiedades
sangradas. Para datos profundamente anidados con entradas de array repetidas, el anidamiento basado
en sangría de YAML es genuinamente menos verboso que los encabezados [[section]] de
TOML. TOML tampoco tiene equivalente al sistema de anclas y alias de YAML, por lo que no puedes
definir un bloque compartido una vez y referenciarlo en otro lugar. Si te encuentras duplicando
el mismo conjunto de valores en múltiples tablas TOML, estás obligado a copiarlos manualmente.
Dónde gana cada formato en la práctica
YAML gana por defecto en estos ecosistemas:
- GitHub Actions. Toda la sintaxis de workflow es YAML. No tienes elección aquí, y está bien — la sangría se corresponde bien con la estructura anidada de jobs, pasos y condiciones.
- Kubernetes. Cada manifiesto — Deployments, Services, ConfigMaps, reglas Ingress — es YAML. El modelo de objetos de Kubernetes está profundamente anidado, y YAML lo maneja con elegancia.
- Docker Compose. Definiciones de servicios, redes, volúmenes — todo YAML. Los comentarios explicando por qué se expone un puerto o por qué se usa un intervalo de healthcheck específico son parte de la documentación.
- Ansible. Playbooks, roles, archivos de variables — YAML en todas partes. El soporte de comentarios se usa activamente para explicar parámetros de tareas no obvios.
TOML gana cuando controlas el formato:
- Proyectos Rust.
Cargo.tomles el estándar de oro. Declaraciones de dependencias, feature flags, perfiles de build — todo en TOML. Los tipos explícitos significan que las cadenas de versión como"1.0.0"permanecen como cadenas. - Proyectos Python.
pyproject.tomlse ha convertido en el estándar para metadatos de proyectos Python, configuración de build y ajustes de herramientas (Black, isort, mypy, pytest todos leen desde él). - Configuración de herramientas donde la ambigüedad causa bugs. Si tu configuración contiene cadenas de versión, códigos de país u otros valores que podrían parecer booleanos o números, los tipos explícitos de TOML eliminan toda una categoría de sorpresas de análisis.
- Configuraciones planas. Cuando tu configuración es principalmente pares clave-valor a uno o dos niveles de anidamiento, TOML es más legible que YAML y más limpio que JSON.
La guía de decisión práctica
La mayor parte del tiempo el ecosistema toma la decisión por ti. GitHub Actions es YAML. Kubernetes es YAML. Rust es TOML. El toolchain de Python es TOML. Cuando realmente tienes libre elección, usa esto como guía:
- Usa YAML cuando la herramienta lo exige — plataformas CI/CD, Kubernetes, charts Helm, Docker Compose, Ansible. Luchar contra la convención cuesta más de lo que ahorra.
- Usa YAML cuando necesitas anclas y alias para mantener una configuración compleja DRY — no hay equivalente en TOML.
- Usa TOML para manifiestos de proyectos y configuración de herramientas que controlas — metadatos de paquetes, ajustes de linters, configuración de build.
- Usa TOML cuando tu configuración contiene valores que YAML 1.1 podría malinterpretar — cadenas de versión, códigos de país, cualquier cosa que se parezca a un booleano o número.
- Pon entre comillas tus cadenas YAML siempre que el valor pueda confundirse con
un booleano, número o null. Especialmente: números de versión, códigos de país, cualquier cosa que
empiece con un dígito, y valores como
yes,no,on,off.
Trabajando con ambos formatos
¿Necesitas validar o formatear un archivo de configuración? El Formateador TOML maneja archivos TOML, y el Formateador YAML cubre YAML. Si necesitas migrar una configuración de un formato a otro — por ejemplo, convertir una configuración de herramienta basada en YAML a TOML para un proyecto que lo prefiere — los conversores TOML a JSON y YAML a JSON producen ambos JSON, que luego puedes convertir a tu formato de destino. A veces pasar por JSON como paso intermedio es el camino más fiable.
Algo que vale la pena saber: ambos formatos tienen sus propios subconjuntos compatibles con JSON. El JSON válido es YAML válido (YAML es un superconjunto de JSON). TOML no tiene esa relación con JSON, pero la especificación TOML se mantiene intencionalmente simple — es una lectura corta, lo que no se puede decir de la especificación YAML 1.2 completa.
Conclusión
TOML y YAML no compiten realmente. Se han asentado en nichos diferentes basados en sus prioridades de diseño. Los tipos implícitos y la estructura basada en sangría de YAML lo convierten en la opción natural para configuraciones grandes y anidadas mantenidas por equipos — piensa en manifiestos de Kubernetes y workflows de GitHub Actions. Los tipos explícitos y la estructura de secciones planas de TOML lo convierten en la opción natural para manifiestos de proyectos y configuración de herramientas donde una cadena de versión o código de país mal leído causaría un bug real.
Lo único que debes recordar: si estás escribiendo YAML y tienes valores que parecen
booleanos, números o nulos — ponlos entre comillas. Es el único hábito que previene la mayoría
de los bugs de configuración relacionados con YAML. Y si estás empezando un nuevo proyecto y
puedes elegir libremente tu formato de configuración, TOML vale la pena. El
repositorio GitHub
de TOML tiene buenos ejemplos, y una vez que has escrito un Cargo.toml o un
pyproject.toml, el atractivo del formato queda bastante claro.