erori (error) în Go

Erori, panic și recover Go 1.0 (împachetare cu %w: Go 1.13)

În Go erorile sunt valori: funcțiile returnează un error, iar apelantul verifică if err != nil.

Sintaxă
type error interface {
    Error() string
}

rezultat, err := functie()
if err != nil {
    return fmt.Errorf("context: %w", err)
}

Ce face erori (error)

error este o interfață predefinită cu o singură metodă, Error() string. O funcție care poate eșua returnează, ca ultimă valoare, un error: nil înseamnă succes, orice altă valoare descrie problema. Apelantul verifică imediat if err != nil și decide: tratează eroarea, o returnează mai departe sau oprește programul. Go nu are excepții.

Erori simple se creează cu errors.New("mesaj") sau fmt.Errorf("..."). Cu verbul %w, fmt.Errorf împachetează eroarea originală adăugând context, iar errors.Is(err, ErrX) și errors.As(err, &tinta) o găsesc și prin straturile de împachetare.

Erorile cunoscute dinainte se declară ca variabile de pachet (var ErrStocEpuizat = errors.New(...)), iar pentru erori cu date suplimentare se definește un tip propriu cu metoda Error(). Mesajele de eroare se scriu cu literă mică și fără punct la final, pentru că se combină: citire config: deschidere fișier: no such file.

Exemple

Exemplul 1: Returnarea și verificarea erorilor

package main

import (
    "errors"
    "fmt"
)

var ErrStocEpuizat = errors.New("stoc epuizat")

func comanda(stoc map[string]int, produs string, bucati int) error {
    disponibil, ok := stoc[produs]
    if !ok {
        return fmt.Errorf("produs necunoscut: %q", produs)
    }
    if bucati > disponibil {
        return fmt.Errorf("comanda de %d x %s: %w", bucati, produs, ErrStocEpuizat)
    }
    stoc[produs] -= bucati
    return nil
}

func main() {
    stoc := map[string]int{"cafea": 3}
    for _, c := range []struct {
        produs string
        n      int
    }{{"cafea", 2}, {"ceai", 1}, {"cafea", 5}} {
        err := comanda(stoc, c.produs, c.n)
        switch {
        case err == nil:
            fmt.Println("ok, mai sunt", stoc[c.produs])
        case errors.Is(err, ErrStocEpuizat):
            fmt.Println("reaprovizionare necesară:", err)
        default:
            fmt.Println("eroare:", err)
        }
    }
}

Rezultat

ok, mai sunt 1
eroare: produs necunoscut: "ceai"
reaprovizionare necesară: comanda de 5 x cafea: stoc epuizat

Exemplul 2: Tip propriu de eroare și errors.As

errors.As găsește *EroareValidare chiar dacă a fost împachetată cu %w.

package main

import (
    "errors"
    "fmt"
)

type EroareValidare struct {
    Camp  string
    Motiv string
}

func (e *EroareValidare) Error() string {
    return e.Camp + ": " + e.Motiv
}

func valideaza(varsta int) error {
    if varsta < 18 {
        return &EroareValidare{Camp: "vârsta", Motiv: "trebuie să fie cel puțin 18"}
    }
    return nil
}

func inregistreaza(nume string, varsta int) error {
    if err := valideaza(varsta); err != nil {
        return fmt.Errorf("înregistrare %s: %w", nume, err)
    }
    return nil
}

func main() {
    err := inregistreaza("Radu", 16)
    fmt.Println(err)
    var ev *EroareValidare
    if errors.As(err, &ev) {
        fmt.Println("câmpul greșit:", ev.Camp)
    }
    fmt.Println(inregistreaza("Ana", 25))
}

Rezultat

înregistrare Radu: vârsta: trebuie să fie cel puțin 18
câmpul greșit: vârsta
<nil>

Exemplul 3: Erori din biblioteca standard

os.ReadFile returnează o eroare care se poate verifica cu errors.Is(err, fs.ErrNotExist).

package main

import (
    "errors"
    "fmt"
    "io/fs"
    "os"
    "strconv"
)

func main() {
    _, err := os.ReadFile("setari.json")
    if errors.Is(err, fs.ErrNotExist) {
        fmt.Println("fișierul nu există, folosesc setările implicite")
    }

    _, err = strconv.Atoi("12a")
    fmt.Println(err)
}

Rezultat

fișierul nu există, folosesc setările implicite
strconv.Atoi: parsing "12a": invalid syntax

Greșeli frecvente

  • Ignorarea erorii cu _: programul continuă cu valori greșite.
  • Compararea mesajelor (err.Error() == "stoc epuizat"): fragilă. Folosește errors.Is cu o eroare declarată.
  • %v în loc de %w în fmt.Errorf: eroarea originală se pierde, iar errors.Is nu o mai găsește.
  • Returnarea unui pointer nil de tip concret ca error: interfața nu mai este nil, iar err != nil devine adevărat. Returnează direct nil.

Comenzi înrudite

  • panic și recover: panic oprește execuția normală pentru erori grave; recover, apelat într-un defer, poate opri panic-ul.
  • if și else: Execută un bloc de cod doar dacă o condiție este adevărată; else tratează cazul contrar.
  • interface și interfețe: Interfața descrie un set de metode; orice tip care le are o implementează automat, fără declarație explicită.
  • type assertion: Scoate valoarea concretă dintr-o interfață: x.(T) verifică dacă interfața ține tipul T și îl returnează.