Wer ein Rust-Projekt aufschlägt, findet eine Cargo.toml. Wer ein GitHub-Repository
klont, findet einen .github/workflows/-Ordner voller YAML. Beide Formate erledigen auf der
Oberfläche dieselbe Aufgabe — strukturierte Konfigurationen speichern, die Menschen bearbeiten — aber sie
machen sehr unterschiedliche Kompromisse. Wenn YAML einen Wert schon einmal still verändert hat, oder man
sich gefragt hat, warum Rust für sein Paketmanifest TOML statt YAML gewählt hat — dieser Artikel gibt
die Antwort.
Der Kernunterschied: Explizit vs. Implizit
Der grundlegende Unterschied zwischen TOML und YAML liegt darin, wie viel der Parser raten darf.
TOML ist explizit: Jeder Wert hat einen
eindeutigen Typ. Strings sind immer in Anführungszeichen. Booleans sind genau true
oder false. Datumsangaben sind erstklassige Werte mit eigener Syntax. Es gibt keine
implizite Typumwandlung — der Parser versucht nicht, clever zu sein.
YAML geht den anderen Weg.
Es versucht, bequem zu sein: Man braucht keine Anführungszeichen um die meisten Strings, und der
Parser leitet Typen aus dem Erscheinungsbild des Werts ab. Diese Inferenz ist das Problem. Die YAML-1.1-
Spezifikation (von vielen Tools noch verwendet) behandelt yes, no,
on, off, true und false alle als Booleans.
Sie behandelt nicht-zitierte Versionsstrings wie 1.0 als Floats. Und dann gibt es
das Norway-Problem.
YAMLs berühmte Fallen
Das Norway-Problem wurde in DevOps-Kreisen zu einem geflügelten Wort. In YAML 1.1 wird der
Ländercode NO als Boolean false geparst. Eine Konfiguration, die ISO-Ländercodes
auf Einstellungen abbildet, würde Norwegens Eintrag stillschweigend in einen Boolean umwandeln. Die
YAML-1.2-Spezifikation
hat das behoben, aber viele weit verbreitete Parser — darunter PyYAMLs Standardmodus bis vor kurzem —
verwenden noch YAML 1.1. Man sollte prüfen, welche Spec-Version das eigene Tooling tatsächlich
implementiert, bevor man ihm vertraut.
NO,
Yes, on und off alle zu Booleans. Die Lösung ist einfach —
Strings in Anführungszeichen setzen — aber das Problem ist, dass es lautlos passiert. Die Konfiguration
lädt ohne Fehler und man bekommt einen Boolean, wo man einen String erwartet hat.# 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"- Oktale Literale. In YAML 1.1 wird
010als8(oktal) geparst, nicht als10. Das spielt eine Rolle bei Dateiberechtigungswerten wie0755. - Tab vs. Leerzeichen. YAML verbietet Tab-Zeichen bei der Einrückung. Code aus einem Editor mit Tab-Konfiguration einfügen und man bekommt einen kryptischen Parse-Fehler — oder schlimmer noch, eine stille Fehlausrichtung.
- Implizite Nulls. Ein Schlüssel ohne Wert wird zu
null. Bei manueller Bearbeitung leicht zu erstellen. - Einrückungssensitivität. Ein extra Leerzeichen und ein Wert verschiebt sich lautlos von einem Elternelement zum anderen. Kein Fehler, nur falsche Daten.
TOML: Wie explizite Typen tatsächlich aussehen
TOMLs Typsystem ist in der
TOML v1.0-Spezifikation beschrieben.
Strings müssen in Anführungszeichen stehen (einfach oder doppelt). Booleans sind true oder
false und sonst nichts. Integer sind Integer. Floats sind Floats. Und Datumsangaben —
etwas, das weder JSON noch YAML als nativen Typ hat — sind erstklassige Werte in 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 = 20Der Ländercode NO ist in TOML einfach ein String, weil er nicht in Anführungszeichen
steht, aber TOML leitet Typen nicht aus string-ähnlichen Werten ab — nicht-zitierte Werte folgen strikten
syntaktischen Regeln. Um ein String zu sein, braucht er Anführungszeichen. TOML hat einfach nicht den
impliziten Umwandlungsmechanismus, der YAMLs Fallen verursacht.
Seite an Seite: Eine CI-Pipeline-Konfiguration
Hier ist ein GitHub-Actions-Workflow — das ist YAMLs Heimgebiet. Das Format passt natürlich, weil GitHub Actions YAML erwartet, die einrückungsbasierte Verschachtelung die logische Struktur widerspiegelt und Kommentare zum Erklären nicht-offensichtlicher Schritt-Konfigurationen unerlässlich sind.
# .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 }}Und hier ist das äquivalente Projektmanifest in TOML — hier glänzt TOML. Man vergleiche eine echte Cargo.toml- Struktur mit dem, wie dieselbe Konfiguration in YAML aussehen würde:
# 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 = 1TOMLs Schwächen: Array of Tables
TOML hat durchaus raue Kanten. Die Syntax für Arrays von Tabellen — [[doppelte Klammern]]
— ist eines der verwirrenderen Dinge in der Spezifikation. So drückt man aus, was JSON als Array von
Objekten schreiben würde, und es liest sich anfangs seltsam.
# 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" }
# ]}In YAML liest sich dieselbe Struktur natürlicher — einfach eine Liste mit eingerückten
Eigenschaften. Bei tief verschachtelten Daten mit wiederholten Array-Einträgen ist YAMLs
einrückungsbasierte Verschachtelung wirklich weniger ausführlich als TOMLs [[Abschnitt]]-
Header. TOML hat auch kein Äquivalent zu YAMLs Anker-und-Alias-System, sodass man keine gemeinsamen
Blöcke einmal definieren und anderswo referenzieren kann. Wenn man dieselben Werte in mehreren
TOML-Tabellen dupliziert, muss man sie manuell kopieren.
Wo jedes Format in der Praxis gewinnt
YAML gewinnt standardmäßig in diesen Ökosystemen:
- GitHub Actions. Die gesamte Workflow-Syntax ist YAML. Man hat hier keine Wahl, und das ist in Ordnung — die Einrückung bildet die verschachtelte Struktur aus Jobs, Schritten und Bedingungen gut ab.
- Kubernetes. Jedes Manifest — Deployments, Services, ConfigMaps, Ingress-Regeln — ist YAML. Das Kubernetes-Objektmodell ist tief verschachtelt, und YAML verarbeitet das elegant.
- Docker Compose. Service-Definitionen, Netzwerke, Volumes — alles YAML. Kommentare, die erklären, warum ein Port exponiert wird oder warum ein bestimmtes Healthcheck-Intervall verwendet wird, sind Teil der Dokumentation.
- Ansible. Playbooks, Rollen, Variablendateien — durchgehend YAML. Die Kommentarunterstützung wird aktiv genutzt, um nicht-offensichtliche Task-Parameter zu erklären.
TOML gewinnt, wenn man das Format kontrolliert:
- Rust-Projekte.
Cargo.tomlist der Goldstandard. Abhängigkeitsdeklarationen, Feature-Flags, Build-Profile — alles in TOML. Die expliziten Typen bedeuten, dass Versionsstrings wie"1.0.0"Strings bleiben. - Python-Projekte.
pyproject.tomlist zum Standard für Python-Projektmetadaten, Build-Konfigurationen und Tool-Einstellungen geworden (Black, isort, mypy, pytest lesen alle davon). - Tool-Konfigurationen, bei denen Mehrdeutigkeit Fehler verursacht. Wenn die Konfiguration Versionsstrings, Ländercodes oder andere Werte enthält, die wie Booleans oder Zahlen aussehen könnten, eliminieren TOMLs explizite Typen eine ganze Kategorie von Parse-Überraschungen.
- Flache Konfigurationen. Wenn die Konfiguration hauptsächlich aus Schlüssel-Wert-Paaren auf einer oder zwei Ebenen besteht, ist TOML lesbarer als YAML und sauberer als JSON.
Der praktische Entscheidungsleitfaden
Meistens wird die Entscheidung durch das Ökosystem getroffen. GitHub Actions ist YAML. Kubernetes ist YAML. Rust ist TOML. Python-Tooling ist TOML. Wenn man tatsächlich die freie Wahl hat, dient dieser Leitfaden:
- YAML verwenden, wenn das Tooling es vorschreibt — CI/CD-Plattformen, Kubernetes, Helm-Charts, Docker Compose, Ansible. Gegen die Konvention anzukämpfen kostet mehr, als es spart.
- YAML verwenden, wenn Anker und Aliase benötigt werden, um eine komplexe Konfiguration DRY zu halten — es gibt kein TOML-Äquivalent.
- TOML verwenden für Projektmanifeste und Tool-Konfigurationen, die man kontrolliert — Paketmetadaten, Linter-Einstellungen, Build-Konfigurationen.
- TOML verwenden, wenn die Konfiguration Werte enthält, die YAML 1.1 möglicherweise falsch interpretiert — Versionsstrings, Ländercodes, alles, was einem Boolean oder einer Zahl ähnelt.
- YAML-Strings in Anführungszeichen setzen, wenn der Wert mit einem Boolean, einer Zahl oder
Null verwechselt werden könnte. Besonders: Versionsnummern, Ländercodes, alles, das mit einer Ziffer beginnt,
und Werte wie
yes,no,on,off.
Mit beiden Formaten arbeiten
Muss man eine Konfigurationsdatei validieren oder schön formatieren? Der TOML-Formatter verarbeitet TOML-Dateien, und der YAML-Formatter deckt YAML ab. Wenn eine Konfiguration von einem Format in das andere migriert werden muss — etwa eine YAML-basierte Tool-Konfiguration in TOML für ein Projekt, das das bevorzugt — geben der TOML zu JSON- und der YAML zu JSON-Konverter beide JSON aus, das dann in das Zielformat konvertiert werden kann. Der Weg über JSON als Zwischenschritt ist manchmal der zuverlässigste.
Es lohnt sich zu wissen: Beide Formate haben JSON-kompatible Teilmengen. Gültiges JSON ist gültiges YAML (YAML ist eine Obermenge von JSON). TOML hat diese Beziehung mit JSON nicht, aber die TOML-Spezifikation ist absichtlich einfach gehalten — sie ist kurz zu lesen, was man von der vollständigen YAML-1.2-Spezifikation nicht behaupten kann.
Zusammenfassung
TOML und YAML konkurrieren eigentlich nicht. Sie haben sich aufgrund ihrer Design-Prioritäten in verschiedene Nischen eingefunden. YAMLs implizite Typen und einrückungsbasierte Struktur machen es zur natürlichen Wahl für große, verschachtelte Konfigurationen, die von Teams gepflegt werden — wie Kubernetes- Manifeste und GitHub-Actions-Workflows. TOMLs explizite Typen und flache Abschnittsstruktur machen es zur natürlichen Wahl für Projektmanifeste und Tool-Konfigurationen, bei denen ein falsch gelesener Versionsstring oder Ländercode einen echten Fehler verursachen würde.
Das Wichtigste zum Mitnehmen: Wenn man YAML schreibt und Werte hat, die wie Booleans, Zahlen
oder Nulls aussehen könnten — diese in Anführungszeichen setzen. Das ist die eine Gewohnheit, die die
meisten YAML-bedingten Konfigurationsfehler verhindert. Und wenn man ein neues Projekt startet und das
Konfigurationsformat frei wählen kann, ist TOML einen Blick wert. Das
TOML-GitHub-Repository
enthält gute Beispiele, und sobald man eine Cargo.toml oder pyproject.toml
geschrieben hat, wird der Reiz des Formats sehr deutlich.