Menu

Golang Type Assertion: x.(T), Comma-ok und Type Switch

Eine Type Assertion holt den konkreten Wert aus einem Interface. x.(T), die Comma-ok-Form ohne Panic, Assertion auf ein anderes Interface, Type Switches und errors.As für gewrappte Fehler.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

x.(T): den Wert aus einem Interface holen

Ein Interface-Wert verbirgt den konkreten Typ, den er enthält. Eine Type Assertion x.(T) fragt ihn zurück ab.

Ausgabe:

gopher 6
0 false
string of length 6

x muss einen Interface-Typ haben. Eine Assertion auf einen konkreten Typ kompiliert nicht: invalid operation: s (variable of type string) is not an interface.

Die Form mit einem Wert löst eine Panic aus

Ist die Assertion falsch, löst die Form mit einem Wert eine Panic mit einer Meldung aus, die beide Typen nennt:

Ausgabe:

recovered: interface conversion: interface {} is int, not string

Nutz die Form mit einem Wert nur, wenn ein anderer Typ ein Programmierfehler wäre, bei dem das Programm abstürzen soll. Überall sonst nimmst du Comma-ok. Bei einem Fehlschlag liefert Comma-ok den Nullwert von T und false.

Auch eine Assertion auf ein nil-Interface schlägt fehl: var v any; v.(string) löst eine Panic mit interface conversion: interface {} is nil, not string aus, und die Comma-ok-Form liefert "", false.

Assertion auf ein anderes Interface

T kann ein Interface-Typ sein. Dann gelingt die Assertion, wenn der dynamische Wert T implementiert, und das Ergebnis behält denselben dynamischen Wert. So prüft die Standardbibliothek optionale Fähigkeiten.

io.Copy prüft, ob seine Quelle io.WriterTo implementiert, und nutzt das dann. fmt prüft auf Stringer und error. Mit diesem Muster bleibt ein kleines Interface klein, und Aufrufer profitieren trotzdem von reichhaltigeren Implementierungen.

Type Switch

Kann ein Wert einer von mehreren Typen sein, ersetzt ein Type Switch eine Kette von Assertions. x.(type) ist nur innerhalb eines switch gültig.

Ausgabe:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

Regeln, die du kennen solltest:

  • In einem Case mit einem Typ hat x diesen Typ. In einem Case mit mehreren Typen und in default hat x den Typ des ursprünglichen Interface (hier any).
  • Die Cases werden der Reihe nach geprüft. Setz spezifischere Interfaces vor allgemeinere, da ein Wert mehrere erfüllen kann.
  • case nil passt nur auf ein nil-Interface, nicht auf ein Interface, das einen nil-Pointer enthält.
  • In einem Type Switch gibt es kein fallthrough.

errors.As: die Assertion für gewrappte Fehler

Fehler werden oft mit Kontext gewrappt: fmt.Errorf("load config: %w", err). Eine direkte Type Assertion betrachtet nur den äußersten Fehler und übersieht den darin. errors.As läuft die Kette entlang.

Ausgabe:

type assertion finds it: false
errors.As finds it: open /no/such/file

Nutz eine Type Assertion auf Fehler nur, wenn du weißt, dass der Fehler nicht gewrappt ist, und das ist in der Praxis fast nie der Fall. Die Seite zu eigenen Fehlertypen behandelt errors.As, errors.Is und das Definieren eigener Fehlertypen.

Assertions vs Konvertierungen vs Generics

Du hastDu willstNimm
intfloat64Konvertierung: float64(n)
any mit einem int darindas intAssertion: v.(int)
any mit mehreren möglichen Typennach Typ verzweigenType Switch
einen error, der gewrappt sein kanneinen bestimmten Fehlertyperrors.As
eine Funktion, die für viele Typen funktioniertSicherheit zur CompilezeitGenerics

Code voller any und Type Switches ist oft ein Zeichen, dass Generics oder ein richtiges Interface die Absicht besser ausdrücken und Fehler schon beim Kompilieren finden würden.

Häufige Fehler

  • Die Panic-Form auf nicht vertrauenswürdigen Daten. Dekodiertes JSON, Map-Werte vom Typ any, Plugin-Eingaben: immer Comma-ok.
  • Assertion auf den falschen Zahlentyp. JSON-Zahlen werden in any als float64 dekodiert, also schlägt v.(int) bei ihnen fehl.
  • Assertion auf einen Werttyp, wenn ein Pointer gespeichert ist. Enthält das Interface *User, schlägt v.(User) fehl. Nimm v.(*User).
  • Type Assertions auf Fehler. Nimm errors.As.

Häufig gestellte Fragen

Was ist eine Type Assertion in Go?

Ein Ausdruck x.(T), wobei x ein Interface-Wert ist. Ist T ein konkreter Typ, liefert er den in x gespeicherten Wert als T. Ist T ein Interface-Typ, prüft er, ob der gespeicherte Wert auch T implementiert. Die Form mit einem Wert löst eine Panic aus, wenn die Prüfung fehlschlägt; die Form mit zwei Werten v, ok := x.(T) meldet es in ok.

Wie prüfe ich den Typ eines Interface-Werts in Go?

Mit einem Type Switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. In jedem Case hat v den Typ dieses Case. Für einen einzelnen Typ ist die Comma-ok-Assertion s, ok := x.(string) kürzer. fmt.Printf("%T", x) gibt zum Debuggen den Typnamen aus.

Was ist der Unterschied zwischen Type Assertion und Typkonvertierung in Go?

Eine Konvertierung T(x) macht aus einem Wert eines Typs einen anderen, etwa float64(n); der Compiler entscheidet, ob sie erlaubt ist, und zur Laufzeit kann sie nicht fehlschlagen. Eine Assertion x.(T) funktioniert nur auf Interface-Werten und prüft zur Laufzeit, welcher Typ darin steckt. Für ein any, das ein int enthält, kannst du nicht int(x) schreiben; du brauchst x.(int).

Sollte ich Fehlertypen mit einer Type Assertion prüfen?

Nein. Nimm errors.As(err, &target). Eine direkte Assertion err.(*MyError) betrachtet nur den äußeren Fehler und schlägt fehl, sobald der Fehler mit fmt.Errorf("...: %w", err) gewrappt ist. errors.As läuft die Wrap-Kette entlang.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S