panic și recover în Go

panic oprește execuția normală pentru erori grave; recover, apelat într-un defer, poate opri panic-ul.

Sintaxă
func panic(v any)
func recover() any

defer func() {
    if r := recover(); r != nil {
        // panic oprit
    }
}()

Ce face panic și recover

panic(v) oprește imediat funcția curentă și începe să „desfacă” stiva: rulează funcțiile amânate cu defer ale fiecărei funcții, de jos în sus. Dacă nimeni nu oprește panic-ul, programul se încheie cu mesajul panic: ..., stiva de apeluri și codul de ieșire 2. Go însuși provoacă panic la erori de programare: index în afara slice-ului, pointer nil, împărțire întreagă la zero, scriere într-un map nil.

recover() oprește un panic și returnează valoarea dată lui panic. Funcționează doar dacă este apelat direct într-o funcție amânată cu defer; în rest returnează nil. După recover, funcția în care a apărut panic-ul se termină normal, cu valorile rezultat de atunci.

panic nu înlocuiește erorile: problemele obișnuite (fișier lipsă, date greșite) se returnează ca error. Panic se folosește pentru situații imposibile sau erori de programare. Un server web folosește recover ca o cerere cu probleme să nu oprească tot serverul.

Parametri

Parametru Descriere
v Orice valoare (de obicei un text sau un error); este returnată de recover și afișată dacă programul se oprește.

Ce returnează

panic nu se întoarce niciodată. recover returnează valoarea panic-ului sau nil dacă nu există panic.

Exemple

Exemplul 1: Transformarea unui panic în eroare

Împărțirea la zero cu întregi provoacă panic; recover din defer îl transformă în error prin rezultatul numit.

package main

import "fmt"

func imparte(a, b int) (rezultat int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("calcul eșuat: %v", r)
        }
    }()
    return a / b, nil
}

func main() {
    fmt.Println(imparte(10, 3))
    fmt.Println(imparte(10, 0))
    fmt.Println("programul continuă")
}

Rezultat

3 <nil>
0 calcul eșuat: runtime error: integer divide by zero
programul continuă

Exemplul 2: Ordinea: defer rulează, apoi recover

package main

import "fmt"

func proceseaza(comenzi []string, i int) {
    defer fmt.Println("  curățenie pentru comanda", i)
    fmt.Println("  procesez", comenzi[i])
}

func main() {
    comenzi := []string{"pizza", "salată"}
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recuperat:", r)
        }
    }()
    for i := range 3 {
        proceseaza(comenzi, i)
    }
    fmt.Println("nu se mai ajunge aici")
}

Rezultat

  procesez pizza
  curățenie pentru comanda 0
  procesez salată
  curățenie pentru comanda 1
  curățenie pentru comanda 2
recuperat: runtime error: index out of range [2] with length 2

Exemplul 3: Panic propriu pentru o situație imposibilă

Un panic necapturat oprește programul cu codul 2 și afișează stiva de apeluri.

package main

import "fmt"

func zi(n int) string {
    zile := []string{"luni", "marți", "miercuri", "joi", "vineri", "sâmbătă", "duminică"}
    if n < 1 || n > 7 {
        panic(fmt.Sprintf("zi invalidă: %d", n))
    }
    return zile[n-1]
}

func main() {
    fmt.Println(zi(3))
    fmt.Println(zi(9))
}

Rezultat

miercuri
panic: zi invalidă: 9

goroutine 1 [running]:
main.zi(0x104a12c18?)
	/home/ana/proiect/main.go:8 +0xf0
main.main()
	/home/ana/proiect/main.go:15 +0x5c
exit status 2

Greșeli frecvente

  • Folosirea lui panic pentru erori obișnuite, ca o excepție: în Go se returnează error.
  • recover() apelat direct în funcție, nu într-un defer, sau într-o funcție apelată de funcția amânată: returnează nil și nu oprește nimic.
  • recover într-o goroutine nu oprește panic-ul altei goroutine: fiecare goroutine își are propriul defer/recover.

Comenzi înrudite

  • defer: Amână un apel de funcție până la ieșirea din funcția curentă; folosit pentru închideri și curățenie.
  • erori (error): În Go erorile sunt valori: funcțiile returnează un error, iar apelantul verifică if err != nil.
  • go și goroutine: Cuvântul go pornește o funcție într-o goroutine: un fir de execuție ușor, care rulează în paralel cu restul programului.