Leçon 5/11 7 min

Fonctions et erreurs

Apprenez les fonctions Go, le retour multiple, la gestion d'erreurs explicite, defer, panic et recover.

Les fonctions

Une fonction en Go se déclare avec func. Simple, lisible, pas de surprises :

func saluer(nom string) string {
    return "Bonjour, " + nom + " !"
}

func main() {
    message := saluer("Alice")
    fmt.Println(message)
}

Remarquez que le type de retour (string) est déclaré après les paramètres, pas avant comme en C ou Java. C'est un choix de design de Go pour améliorer la lisibilité.

Retours multiples : la force de Go

En Go, une fonction peut retourner plusieurs valeurs. C'est là que Go brille par rapport aux autres langages :

func diviser(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division par zéro")
    }
    return a / b, nil
}

func main() {
    resultat, err := diviser(10, 3)
    if err != nil {
        fmt.Println("Erreur:", err)
        return
    }
    fmt.Println(resultat) // 3.3333333333333335
}

Le pattern valeur, err := fonction() est le cœur de Go. Vous le verrez partout.

Une fonction Go renvoie souvent deux valeurs à la fois : le résultat et une erreur, récupérés d'un coup dans deux variables. diviser(10, 3) float64 error resultat = 3.33 err = nil
Deux valeurs renvoyées d'un coup : le résultat ET l'erreur. resultat, err := diviser(10, 3).

Pas de try/catch : et c'est voulu

Si vous venez de JavaScript ou Python, vous connaissez try/catch. Go fait un choix radicalement différent : pas d'exceptions. Chaque erreur est une valeur retournée que vous devez gérer explicitement.

file, err := os.Open("config.json")
if err != nil {
    fmt.Println("Impossible d'ouvrir le fichier:", err)
    return
}
defer file.Close()
// Utiliser file...

Ce pattern if err != nil peut sembler répétitif au début. Mais il force le développeur à penser à chaque erreur possible. Résultat : les programmes Go plantent rarement en production.

La philosophie de Go : "Les erreurs sont des valeurs, pas des événements exceptionnels."

defer, panic, recover

defer repousse l'exécution d'une instruction à la fin de la fonction. Idéal pour le nettoyage :

Prédisez avant de lire

Cette boucle empile trois defer fmt.Println(i) : Avant de dérouler : dans quel ordre les nombres s'affichent-ils quand la fonction se termine, et quelles valeurs : 0 1 2, ou 2 1 0, ou autre chose ?

for i := 0; i < 3; i++ {
    defer fmt.Println(i)
}
Voir la réponse

Ça affiche 2, puis 1, puis 0. Deux règles se combinent. (1) Les defer s'exécutent en ordre LIFO (dernier empilé, premier exécuté), au moment où la fonction retourne. (2) L'argument d'un defer est évalué immédiatement, à l'instant où la ligne defer est rencontrée : defer fmt.Println(i) capture donc la valeur de i à ce tour de boucle (0, puis 1, puis 2), pas sa valeur finale. Les valeurs capturées sont 0, 1, 2, mais exécutées dans l'ordre inverse : 2 1 0. Usage typique de defer : libérer une ressource (fermer un fichier, déverrouiller) juste après l'avoir obtenue, certain que ça s'exécutera à la sortie.

func lireFichier(nom string) {
    file, err := os.Open(nom)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close() // Sera exécuté à la fin, quoi qu'il arrive

    // Lire le fichier...
    fmt.Println("Fichier ouvert avec succès")
}

panic et recover existent pour les situations vraiment catastrophiques (bug dans le programme, pas une erreur utilisateur) :

func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Récupéré:", r)
        }
    }()
    panic("quelque chose de terrible")
}
// Affiche: Récupéré: quelque chose de terrible

Règle d'or : utilisez error pour les erreurs normales, panic uniquement pour les bugs.

À vous d'essayer :

package main

import "fmt"

func diviser(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division par zéro")
    }
    return a / b, nil
}

func main() {
    resultat, err := diviser(10, 3)
    if err != nil {
        fmt.Println("Erreur:", err)
        return
    }
    fmt.Println(resultat)
}

Functions

A Go function is declared with func. Simple, readable, no surprises:

func greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    message := greet("Alice")
    fmt.Println(message)
}

Notice the return type (string) is declared after the parameters, not before like in C or Java. This is a Go design choice to improve readability.

package main

import "fmt"

func greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    message := greet("Alice")
    fmt.Println(message)
}

Multiple returns — Go's strength

In Go, a function can return multiple values. This is where Go shines compared to other languages:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(result) // 3.3333333333333335
}

The value, err := function() pattern is the heart of Go. You'll see it everywhere.

A Go function often returns two values at once: the result and an error, both unpacked into two variables. divide(10, 3) float64 error result = 3.33 err = nil
Two values returned at once: the result AND the error. result, err := divide(10, 3).

No try/catch — and that's on purpose

If you come from JavaScript or Python, you know try/catch. Go makes a radically different choice: no exceptions. Every error is a returned value you must handle explicitly.

file, err := os.Open("config.json")
if err != nil {
    fmt.Println("Cannot open file:", err)
    return
}
defer file.Close()
// Use file...

This if err != nil pattern may seem repetitive at first. But it forces the developer to think about every possible error. Result: Go programs rarely crash in production.

Go's philosophy: "Errors are values, not exceptional events."

defer, panic, recover

defer postpones the execution of a statement until the end of the function. Perfect for cleanup:

Predict before reading

This loop stacks three defer fmt.Println(i) calls. Before scrolling down: in what order do the numbers print when the function returns, and which values: 0 1 2, or 2 1 0, or something else?

for i := 0; i < 3; i++ {
    defer fmt.Println(i)
}
See the answer

It prints 2, then 1, then 0. Two rules combine. (1) Deferred calls execute in LIFO order (last stacked, first executed), when the function returns. (2) The argument of a defer is evaluated immediately, at the moment the defer line is reached: defer fmt.Println(i) captures the value of i at that loop iteration (0, then 1, then 2), not its final value. The captured values are 0, 1, 2, but executed in reverse order: 2 1 0. Typical use of defer: release a resource (close a file, unlock a mutex) right after acquiring it, knowing it will run at function exit no matter what.

func readFile(name string) {
    file, err := os.Open(name)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close() // Will be executed at the end, no matter what

    // Read the file...
    fmt.Println("File opened successfully")
}

panic and recover exist for truly catastrophic situations (bug in the program, not a user error):

func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()
    panic("something terrible")
}
// Prints: Recovered: something terrible

Golden rule: use error for normal errors, panic only for bugs.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

Prompt IA
Avec l'IA

Copiez ce prompt dans Claude ou ChatGPT :

Écris une fonction Go qui lit un fichier JSON et retourne son contenu dans un struct. Gère toutes les erreurs possibles (fichier introuvable, JSON invalide). Explique chaque if err != nil.
💬 Ré-explique sans regarder
Ré-explique sans regarder

Sans relire la réponse de l'IA : avec tes mots, pourquoi chaque appel comme os.Open est suivi d'un if err != nil au lieu d'un try/catch ?

Une bonne explication dit : en Go l'erreur est une valeur retournée, pas un événement qui interrompt le flux. La fonction renvoie (résultat, error) et c'est l'appelant qui décide quoi faire via if err != nil. Avantage : l'erreur est visible dans la signature et impossible à oublier silencieusement ; inconvénient apparent : c'est verbeux. Le compilateur n'oblige pas à lire err, mais l'ignorer (_) est un anti-pattern.
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

L'IA te propose ce code pour lire un fichier de config. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.

data, _ := os.ReadFile("config.json")
var cfg Config
json.Unmarshal(data, &cfg)
fmt.Println(cfg.Port)
À rejeter. Deux erreurs sont avalées : le _ jette l'erreur de os.ReadFile (fichier introuvable ? droits ?) et l'erreur de json.Unmarshal n'est même pas récupérée (JSON invalide passe inaperçu). Si la lecture échoue, data est vide et cfg.Port vaudra silencieusement zéro : un bug fantôme en production. Le réflexe Go : if err != nil { return err } après chaque appel faillible. C'est exactement le piège que la philosophie « les erreurs sont des valeurs » sert à éviter.
🧠 Rappel libre
Rappel libre

Sans remonter dans la leçon : quel est le pattern classique d'appel d'une fonction Go qui peut échouer, et que met-on dans defer ?

Le pattern est résultat, err := fonction() suivi immédiatement de if err != nil { ... return } : on récupère la valeur ET l'erreur, puis on traite l'erreur avant de continuer. Dans defer on met le nettoyage à exécuter à la sortie de la fonction, quoi qu'il arrive : typiquement defer file.Close() juste après une ouverture réussie.
Combien de valeurs une fonction Go peut-elle retourner ?
Comment Go gère-t-il les erreurs ?
Que fait le mot-clé defer ?
Quand utiliser panic en Go ?
Prochaine étape

Vos fonctions renvoient proprement leurs erreurs. Mais une erreur, en Go, c'est quoi au juste ? On donne enfin un comportement à vos types : les méthodes, puis les interfaces qui rendent error si élégant.

Leçon 6 : Méthodes et interfaces →

Une erreur dans cette leçon, un passage flou, une question ? Écrivez-moi : chaque retour améliore ce cours.

Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement