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.
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 :
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.
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:
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
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
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 ?
(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
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)
_ 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
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 ?
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.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 →