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
xdiesen Typ. In einem Case mit mehreren Typen und indefaulthatxden Typ des ursprünglichen Interface (hierany). - Die Cases werden der Reihe nach geprüft. Setz spezifischere Interfaces vor allgemeinere, da ein Wert mehrere erfüllen kann.
case nilpasst 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 hast | Du willst | Nimm |
|---|---|---|
int | float64 | Konvertierung: float64(n) |
any mit einem int darin | das int | Assertion: v.(int) |
any mit mehreren möglichen Typen | nach Typ verzweigen | Type Switch |
einen error, der gewrappt sein kann | einen bestimmten Fehlertyp | errors.As |
| eine Funktion, die für viele Typen funktioniert | Sicherheit zur Compilezeit | Generics |
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
anyalsfloat64dekodiert, also schlägtv.(int)bei ihnen fehl. - Assertion auf einen Werttyp, wenn ein Pointer gespeichert ist. Enthält das Interface
*User, schlägtv.(User)fehl. Nimmv.(*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.