Menu

Golang Errors: Wrap, errors.Is, errors.As und eigene Fehlertypen

Sentinel-Fehler und eigene Fehlertypen definieren, Fehler mit %w wrappen, mit errors.Is und errors.As prüfen, mehrere mit errors.Join zusammenfassen und bei Bedarf Unwrap- und Is-Methoden schreiben.

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

Drei Arten von Fehlern

Go-Code verwendet drei Formen von Fehlern, von der einfachsten zur reichhaltigsten:

  1. Ein Ad-hoc-Fehler: errors.New("...") oder fmt.Errorf("..."), an Ort und Stelle erzeugt. Aufrufer können nur die Meldung lesen.
  2. Ein Sentinel-Fehler: eine Variable auf Paketebene wie io.EOF. Aufrufer können mit errors.Is darauf prüfen.
  3. Ein Fehlertyp: ein Struct mit einer Methode Error(), das Felder trägt. Aufrufer können ihn mit errors.As herausholen und die Felder lesen.

Nimm die einfachste Form, mit der Aufrufer tun können, was sie brauchen.

Sentinel-Fehler

Ein Sentinel ist ein Fehlerwert, der einmal deklariert und über seine Identität verglichen wird. Per Konvention beginnen die Namen mit Err.

Ausgabe:

mug bought
cap is out of stock, notify me later
buy "hat": not found

Zwei Aufrufe von errors.New mit demselben Text erzeugen verschiedene Fehler: errors.New("x") == errors.New("x") ist false. Deshalb muss ein Sentinel eine einzige geteilte Variable sein.

Sentinels werden Teil der API deines Pakets. Sobald Aufrufer auf ErrNotFound prüfen, kannst du nicht aufhören, ihn zurückzugeben, ohne sie zu brechen. Exportiere nur die, nach denen Aufrufer wirklich verzweigen müssen.

Eigene Fehlertypen

Braucht der Aufrufer Details (welches Feld, welcher Statuscode, welche Wartezeit bis zum Retry), definier einen Typ.

Ausgabe:

register: age: must be between 0 and 150
bad field: age

Die Methode hat einen Pointer Receiver, und die Funktion gibt &ValidationError{...} zurück, also ist das Ziel für errors.As ein *ValidationError, und du übergibst dessen Adresse (&ve, ein **ValidationError). Diese Ebene falsch zu treffen ist der klassische Fehler bei errors.As: Mit einem Value Receiver würdest du ValidationError{...} zurückgeben und var ve ValidationError deklarieren. go vet findet den häufigsten Ausrutscher, ve statt &ve zu übergeben: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.

Wrappen mit %w

fmt.Errorf mit %w gibt einen Fehler zurück, der sich an den gewrappten erinnert. Jede Schicht fügt Kontext hinzu und hält die Kette intakt.

Ausgabe:

start: connect db: timeout
  *fmt.wrapError: start: connect db: timeout
  *fmt.wrapError: connect db: timeout
  *errors.errorString: timeout
true
false

Mit %v ist die Meldung dieselbe, aber die Kette ist unterbrochen, also gibt errors.Is false zurück. Nimm %w, wenn Aufrufer die Ursache sehen sollen, und %v, wenn die Ursache ein Implementierungsdetail ist, von dem sie nicht abhängen sollen.

Seit Go 1.20 kann ein Aufruf mehrere Fehler wrappen: fmt.Errorf("%w; %w", err1, err2). errors.Is trifft dann auf jeden der beiden.

errors.Join: mehrere Fehler auf einmal

Validierung und Aufräumen erzeugen oft mehr als einen Fehler. errors.Join (Go 1.20) bündelt sie.

Ausgabe:

name: required
email: required
age: negative
---
true
true

errors.Join gibt nil zurück, wenn jedes Argument nil ist, also braucht die Funktion oben keinen Sonderfall für „keine Fehler“.

Unwrap- und Is-Methoden

errors.Is und errors.As finden gewrappte Fehler, indem sie eine Methode Unwrap aufrufen. Ein eigener Typ, der eine Ursache enthält, sollte sie herausgeben:

Ein Typ, der mehrere Fehler wrappt, implementiert stattdessen Unwrap() []error.

Ein Typ kann auch Is(target error) bool definieren, um selbst über Gleichheit zu entscheiden, etwa um jeden *HTTPError mit demselben Statuscode zu treffen. Das brauchst du selten; einen Sentinel zu wrappen und ihn aus Unwrap zurückzugeben reicht meist.

Die Falle mit dem typisierten nil

Deklarier nie eine Fehlervariable mit deinem konkreten Typ und gib sie über error zurück:

func check() error {
	var err *ValidationError // nil pointer
	// ... no problem found
	return err // non-nil error! Its type is *ValidationError
}

Das if err != nil des Aufrufers ist wahr, weil ein Interface, das einen typisierten nil-Pointer enthält, nicht nil ist. Gib bei Erfolg ein wörtliches nil zurück und typisiere lokale Fehlervariablen als error. Die Seite zu Interfaces erklärt warum.

Welche Art du wählen solltest

Aufrufer müssenBiete an
den Fehlschlag nur loggen oder anzeigenfmt.Errorf("...: %w", err)
nach einer bestimmten Bedingung verzweigeneinen Sentinel var ErrX = errors.New(...)
Details über den Fehlschlag leseneinen Fehlertyp mit Feldern
mehrere unabhängige Fehlschläge sehenerrors.Join

Häufige Fehler

  • Gewrappte Fehler mit == vergleichen. Nimm errors.Is.
  • Einen Nicht-Pointer an errors.As übergeben. Es braucht einen Pointer auf eine Variable des Zieltyps.
  • Einen „Sentinel“ in einer Funktion erzeugen. return errors.New("not found") erzeugt bei jedem Aufruf einen neuen Wert; Aufrufer können nicht dagegen vergleichen.
  • Jeden Fehler exportieren. Jeder exportierte Sentinel und jeder exportierte Typ ist ein API-Versprechen.
  • Auf err.Error() prüfen. Strings sind für Menschen.

Häufig gestellte Fragen

Wie erstelle ich einen eigenen Fehler in Go?

Für eine feste Bedingung deklarierst du einen Sentinel auf Paketebene: var ErrNotFound = errors.New("not found"). Für einen Fehler, der Daten trägt, definierst du einen Typ mit einer Methode Error() string: type ValidationError struct { Field string } und func (e *ValidationError) Error() string { return e.Field + " is invalid" }.

Wie wrappt man einen Fehler in Go?

Mit fmt.Errorf und dem Verb %w: return fmt.Errorf("load user %d: %w", id, err). Die Meldung des neuen Fehlers enthält die alte, und errors.Unwrap, errors.Is und errors.As erreichen das Original. Seit Go 1.20 kann ein einziger Errorf-Aufruf mit mehreren %w-Verben mehrere Fehler wrappen.

Was ist der Unterschied zwischen errors.Is und errors.As?

errors.Is(err, target) beantwortet „steckt genau dieser Fehlerwert irgendwo in der Kette“, für Sentinels wie io.EOF. errors.As(err, &target) beantwortet „gibt es einen Fehler dieses Typs in der Kette“ und speichert ihn gegebenenfalls in target, damit du seine Felder lesen kannst.

Was macht errors.Join?

errors.Join(errs...) (Go 1.20) fasst mehrere Fehler zu einem zusammen. Seine Meldung besteht aus den einzelnen Meldungen, getrennt durch Zeilenumbrüche, nil-Fehler werden weggelassen, und es gibt nil zurück, wenn alle nil sind. errors.Is und errors.As treffen, wenn irgendeiner der zusammengefassten Fehler passt.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S