fmt.State ist der Laufzeit-Kontext, den das fmt-Paket einer eigenen Format-Methode überreicht, sobald diese durch das Formatter-Interface aufgerufen wird. Das Interface vereint zwei Rollen in einem einzigen Objekt: Es ist Schreibziel (durch embedded io.Writer) und Inspektionspunkt für die Modifier, die im Format-String hinter dem Prozent-Zeichen standen — also Width, Precision und die Flags +, -, #, 0, Leerzeichen.

Ohne State wäre Formatter.Format ein blinder Aufruf: Man bekäme das Verb, wüsste aber nicht, wohin geschrieben werden soll und welche Feinjustierung der Nutzer im Format-String wirklich verlangt hat. Die Trennung in vier sehr kleine Methoden — Write, Width, Precision, Flag — sorgt dafür, dass jede Format-Implementierung genau die Information abruft, die sie tatsächlich braucht, und den Rest dem Default überlässt.

Interface-Methoden

State ist bewusst minimal gehalten. Die vier Methoden decken jeden Aspekt eines Format-Verbs ab, der zur Laufzeit relevant ist — und nichts darüber hinaus.

Go state.go
type State interface {
    // Write ist die Output-Senke — State ist ein io.Writer.
    Write(b []byte) (n int, err error)

    // Width liefert die Breite aus dem Format-String.
    // ok == false, wenn keine Breite angegeben wurde.
    Width() (wid int, ok bool)

    // Precision liefert die Präzision aus dem Format-String.
    // ok == false, wenn keine Präzision angegeben wurde.
    Precision() (prec int, ok bool)

    // Flag meldet, ob die Flag-Rune c im Format-String stand.
    Flag(c int) bool
}

Die drei Getter geben keine nackten int-Werte zurück, sondern jeweils ein (int, bool)-Paar. Damit lässt sich sauber unterscheiden, ob der Aufrufer %5d (Breite gesetzt) oder %d (keine Breite) geschrieben hat — beides würde bei einer reinen int-Rückgabe sonst dieselbe Null produzieren und die Default-Logik aushebeln.

Write — die Output-Senke

State ist selbst ein io.Writer. Innerhalb von Format schreibt man also direkt auf den Parameter — es gibt keinen separaten Buffer, keinen Return-String, kein io.WriteString über Umwege. Jedes Byte, das durch die Format-Methode rausgeht, landet exakt dort, wo fmt.Printf, fmt.Sprintf oder fmt.Fprintf es haben wollen.

Go write.go
func (t MyType) Format(s fmt.State, verb rune) {
    // direktes Schreiben in den State
    s.Write([]byte("hallo "))

    // bequemer mit fmt.Fprintf — s ist ein io.Writer
    fmt.Fprintf(s, "welt (%c)", verb)
}

In der Praxis ruft man selten Write direkt mit Byte-Slice auf. Sehr viel häufiger reicht man den State einfach an fmt.Fprintf oder fmt.Fprint weiter — beides funktioniert, weil das Interface die io.Writer-Signatur ohnehin garantiert.

Width und Precision — Verb-Modifier inspizieren

Width() liefert die Zahl direkt nach dem Prozent (%10dwid=10), Precision() die Zahl nach dem Punkt (%.3fprec=3). Beide melden über das ok-Flag, ob der Wert überhaupt im Format-String stand. Genau diese Unterscheidung ermöglicht es, einen Default zu setzen, der nur greift, wenn der Nutzer nichts vorgegeben hat.

Go width-precision.go
func (t MyType) Format(s fmt.State, verb rune) {
    width, hasWidth := s.Width()
    prec, hasPrec := s.Precision()

    if !hasWidth {
        width = 8 // sinnvoller Default
    }
    if !hasPrec {
        prec = 2
    }

    fmt.Fprintf(s, "%*.*f", width, prec, float64(t))
}

Der Trick mit %*.*f ist hier kein Zufall: Das Sternchen lässt fmt die Breite bzw. Präzision aus einem zusätzlichen Argument lesen — so reicht man die vom State gemeldeten Werte sauber durch und überlässt das eigentliche Padding der Standard-Implementierung.

Flag(c int) bool — die fünf Format-Flags

Flag nimmt eine Rune (in Form eines int-Codepoints) und antwortet mit true, wenn dieses Flag im Format-String stand. Insgesamt gibt es fünf Flags, die fmt standardmäßig parst.

Go flags.go
func (t MyType) Format(s fmt.State, verb rune) {
    plus  := s.Flag('+')  // %+v   Vorzeichen erzwingen / Feldnamen
    minus := s.Flag('-')  // %-10v linksbündig pollstern
    hash  := s.Flag('#')  // %#v   alternative Darstellung (z. B. 0x)
    zero  := s.Flag('0')  // %05d  mit Nullen auffüllen
    space := s.Flag(' ')  // % d   Leerzeichen vor positiven Zahlen

    fmt.Fprintf(s, "+:%v -:%v #:%v 0:%v ' ':%v",
        plus, minus, hash, zero, space)
}

Die Rune-Notation '+' ist semantisch dasselbe wie der Codepoint 43. Innerhalb von Format-Methoden ist die Rune-Form deutlich lesbarer und sollte immer bevorzugt werden — der int-Parameter ist nur ein historisches Detail der Interface-Definition.

Typische Verwendung in Formatter.Format

Eine sauber strukturierte Format-Methode geht in drei Schritten vor: zuerst Verb prüfen, dann State inspizieren, schließlich passendes Layout schreiben. Genau diese Reihenfolge spiegelt wider, wie fmt intern auch arbeitet — und macht den Code lesbar für alle, die das Muster aus der Standard-Library kennen.

Go pattern.go
func (t MyType) Format(s fmt.State, verb rune) {
    // 1) Verb-Dispatch
    switch verb {
    case 'v', 's':
        // 2) Modifier inspizieren
        width, hasWidth := s.Width()
        showSign := s.Flag('+')

        // 3) Layout schreiben
        if showSign {
            s.Write([]byte("+"))
        }
        if hasWidth {
            fmt.Fprintf(s, "%-*s", width, t.label())
        } else {
            fmt.Fprint(s, t.label())
        }
    default:
        fmt.Fprintf(s, "%%!%c(MyType=%s)", verb, t.label())
    }
}

Der default-Zweig folgt einer Konvention, die fmt selbst nutzt: Bei einem unbekannten Verb wird %!v(TYP=wert) geschrieben — so erkennt der Nutzer sofort, dass sein Format-String nicht zum Typ passt, ohne dass das Programm crasht.

Praxis 1 — Custom-Decimal mit Width-Padding

Ein klassischer Anwendungsfall: ein Typ, der wie eine Zahl gerendert wird, aber Breite und Ausrichtung (- für linksbündig) selbst respektieren soll. Damit verhält sich der eigene Typ in Spalten-Layouts wie ein nativer numerischer Wert.

Go praxis-padding.go
package main

import (
	"fmt"
	"strings"
)

type Cents int64

func (c Cents) Format(s fmt.State, verb rune) {
	text := fmt.Sprintf("%d.%02d €", int64(c)/100, int64(c)%100)

	width, hasWidth := s.Width()
	if !hasWidth || width <= len(text) {
		s.Write([]byte(text))
		return
	}

	pad := strings.Repeat(" ", width-len(text))
	if s.Flag('-') {
		s.Write([]byte(text + pad)) // linksbündig
	} else {
		s.Write([]byte(pad + text)) // rechtsbündig
	}
}

func main() {
	c := Cents(12345)
	fmt.Printf("[%v]\n", c)
	fmt.Printf("[%10v]\n", c)
	fmt.Printf("[%-10v]\n", c)
}
Output
[123.45 €]
[ 123.45 €]
[123.45 € ]

Die Logik ist bewusst kompakt: Erst wird der eigentliche Text gebaut, dann entscheidet das Vorhandensein einer Width über das Padding und das --Flag über die Richtung. Diese drei Zeilen ersetzen, was sonst in einer separaten String-Bibliothek versteckt wäre — und integrieren sich nahtlos in jeden fmt.Printf-Aufruf.

Praxis 2 — Bytes-Typ mit Hex-Flag-Logik

Das #-Flag schaltet bei vielen Standard-Verben in eine alternative Darstellung — bei %x heißt das den 0x-Prefix mitschreiben, bei %o ein führendes 0. Diese Konvention kann ein eigener Typ ohne Mehraufwand übernehmen, damit Nutzer ihn so behandeln können, wie sie es von int her gewohnt sind.

Go praxis-hex.go
package main

import "fmt"

type Bytes []byte

func (b Bytes) Format(s fmt.State, verb rune) {
	switch verb {
	case 'x':
		if s.Flag('#') {
			s.Write([]byte("0x"))
		}
		for _, c := range b {
			fmt.Fprintf(s, "%02x", c)
		}
	case 'v':
		fmt.Fprintf(s, "Bytes(len=%d)", len(b))
	default:
		fmt.Fprintf(s, "%%!%c(Bytes)", verb)
	}
}

func main() {
	b := Bytes{0xDE, 0xAD, 0xBE, 0xEF}
	fmt.Printf("%x\n", b)
	fmt.Printf("%#x\n", b)
	fmt.Printf("%v\n", b)
}
Output
deadbeef
0xdeadbeef
Bytes(len=4)

Die Verzweigung über s.Flag('#') ist hier das Herzstück: Ein und dasselbe Verb verhält sich je nach Flag unterschiedlich, ohne dass der Aufrufer eine separate Methode oder einen anderen Verb-Buchstaben kennen muss. Genau dieser Mechanismus macht State zum Bindeglied zwischen Format-String-Grammatik und individueller Typ-Logik.

Embedded io.Writer

State erbt Write([]byte) (int, error) — jeder fmt.Fprintf-kompatible Code funktioniert direkt mit dem State-Parameter.

Width / Precision liefern (int, bool)

Das ok-Flag unterscheidet sauber zwischen „nicht gesetzt" und „explizit 0" — Default-Werte greifen nur bei ok == false.

Flag-Konstanten als Rune

Flag('+'), Flag('-'), Flag('#'), Flag('0'), Flag(' ') decken alle fünf Standard-Flags ab — Rune-Literale sind lesbarer als Codepoint-Zahlen.

fmt.Fprintf(state, ...) als Helfer

Statt manuell Bytes zu bauen, einfach den State an fmt.Fprintf weitergeben — spart Boilerplate und nutzt die volle Standard-Formatierung.

Kein direkter Verb-Zugriff über State

Das Verb kommt als separater rune-Parameter in FormatState selbst kennt das Verb nicht und bietet keine Methode dafür.

Custom-Verben sind möglich

Über Formatter.Format darf ein Typ beliebige Verben akzeptieren — Konvention ist ein Fehler-Fallback wie %!v(TYP=wert) für unbekannte.

State ist read-only für Modifier

Width, Precision und Flags lassen sich abfragen, aber nicht ändern — der Format-String bestimmt sie, die Implementierung folgt.

State lebt nur während Format

Den State-Pointer nicht für späteren Gebrauch wegspeichern — er ist nur innerhalb des laufenden Format-Aufrufs gültig.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das fmt-Paket — Formatierte I/O

Zur Übersicht