Retour au cours

backend / go

Fonctions et valeurs multiples

Leçon 41 exercice

Explication

Ce que vous allez apprendre

  • Écrire des fonctions avec plusieurs paramètres et plusieurs valeurs de retour
  • Comprendre pourquoi (valeur, error) est LE pattern central de Go pour signaler un échec
  • Utiliser les valeurs de retour nommées pour documenter l'intention d'une fonction
  • Écrire une fonction variadique (...int) et déballer un slice avec slice...
  • Passer une fonction en paramètre et écrire des closures qui capturent une variable

Dans quel contexte ?

Un développeur qui vient de Python attaque une fonction Go qui interroge une base de données et s'attend à devoir écrire un try/except. Il découvre à la place une fonction qui retourne (*Utilisateur, error), et doit vérifier if err != nil juste après chaque appel. Comprendre ce pattern dès maintenant, avant d'écrire la moindre logique métier, évite de coder des heures avec des erreurs silencieusement ignorées.

D'abord, une fonction Go peut retourner plus d'une valeur

C'est une différence structurelle avec la majorité des langages mainstream : func diviser(a, b float64) (float64, error) retourne à la fois un résultat et une éventuelle erreur, sans avoir besoin d'un tuple explicite ou d'une exception.

Cette possibilité native de retour multiple est ce qui rend possible l'idiome de gestion d'erreurs de Go : au lieu de lever une exception qui remonte silencieusement la pile d'appel, chaque fonction qui peut échouer retourne explicitement son erreur, que l'appelant est obligé de considérer (même s'il peut techniquement l'ignorer avec _).

Une fois ce principe acquis, il faut connaître une variante utile : les retours nommés

Une fonction peut nommer ses valeurs de retour directement dans sa signature, comme func statistiques(notes []int) (min, max, somme int). Ces noms servent de documentation vivante et créent automatiquement les variables correspondantes, initialisées à leur zero value.

Style de retourExempleAvantage
Retour simplefunc carre(x int) intLe plus lisible pour un cas simple
Retour multiplefunc diviser(a, b float64) (float64, error)Standard Go pour signaler un échec
Retour nomméfunc stats(n []int) (min, max int)Documente l'intention, permet un return "naked"

Prérequis

Il faut être à l'aise avec les conditions et boucles (leçon précédente) pour suivre les exemples utilisant for/range à l'intérieur des fonctions présentées ici.

Ensuite, un mécanisme qui étonne au début : les fonctions variadiques

func somme(nombres ...int) int accepte un nombre quelconque d'arguments entiers, qui arrivent à l'intérieur de la fonction sous forme de slice. C'est le mécanisme qui rend possible des appels comme fmt.Println(a, b, c) avec un nombre variable d'arguments.

Pour "déballer" un slice existant dans une fonction variadique, on utilise la syntaxe nombres... au moment de l'appel — un détail syntaxique facile à oublier au début, qui provoque une erreur de compilation claire si omis.

Piège fréquent

Une fonction avec des valeurs de retour nommées peut faire un return "naked" (sans rien après le mot-clé), ce qui retourne implicitement les valeurs actuelles des variables nommées. C'est pratique mais peut rendre le code difficile à suivre si la fonction est longue : réserve cette forme aux fonctions courtes où l'intention reste limpide.

Enfin, les fonctions sont des valeurs comme les autres

Une fonction peut être stockée dans une variable, passée en paramètre à une autre fonction, ou retournée depuis une fonction. C'est ce qui permet d'écrire appliquer(nombres, carre)carre est elle-même une fonction.

Une closure va plus loin : une fonction anonyme peut "capturer" une variable de son environnement et continuer à l'utiliser même après que la fonction englobante s'est terminée, comme dans l'exemple creerCompteur() qui retourne un compteur qui s'incrémente à chaque appel.

Bonne pratique

Vérifie systématiquement if err != nil immédiatement après chaque appel de fonction qui retourne une erreur, sans attendre. Un err ignoré est la source numéro un de bugs difficiles à diagnostiquer en production sur un projet Go.

Maintenant que tu sais organiser une logique en fonctions, l'étape suivante consiste à structurer de vraies données métier avec des types composés : place aux structs et aux méthodes.

Commandes & code

Fonctions et valeurs multiples

Les fonctions Go peuvent retourner plusieurs valeurs : c'est la base de l'idiome de gestion d'erreurs.

go
package main

import (
	"errors"
	"fmt"
)

// Fonction simple
func additionner(a, b int) int {
	return a + b
}

// Types de parametres identiques groupes : (a, b int)
func multiplier(a, b int) int {
	return a * b
}

// Retour multiple : le pattern (valeur, error) est omnipresent en Go
func diviser(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division par zero")
	}
	return a / b, nil
}

// Valeurs de retour nommees : documentent l'intention, retour "naked"
func statistiques(notes []int) (min, max, somme int) {
	if len(notes) == 0 {
		return // retourne les zero values : 0, 0, 0
	}
	min, max = notes[0], notes[0]
	for _, n := range notes {
		somme += n
		if n < min {
			min = n
		}
		if n > max {
			max = n
		}
	}
	return // retourne min, max, somme implicitement
}

// Fonction variadique : nombre d'arguments variable
func somme(nombres ...int) int {
	total := 0
	for _, n := range nombres {
		total += n
	}
	return total
}

// Fonction comme parametre (les fonctions sont des valeurs de premiere classe)
func appliquer(nombres []int, f func(int) int) []int {
	resultat := make([]int, len(nombres))
	for i, n := range nombres {
		resultat[i] = f(n)
	}
	return resultat
}

func main() {
	fmt.Println(additionner(2, 3), multiplier(4, 5))

	resultat, err := diviser(10, 2)
	if err != nil {
		fmt.Println("erreur:", err)
	} else {
		fmt.Println("resultat:", resultat)
	}

	_, err = diviser(10, 0)
	fmt.Println("erreur attendue:", err)

	min, max, total := statistiques([]int{4, 8, 15, 16, 23, 42})
	fmt.Println(min, max, total)

	fmt.Println(somme(1, 2, 3, 4, 5))
	nombres := []int{1, 2, 3}
	fmt.Println(somme(nombres...)) // spread d'un slice

	// Fonction anonyme / closure
	carre := func(x int) int { return x * x }
	fmt.Println(appliquer([]int{1, 2, 3, 4}, carre))

	// Closure capturant une variable externe
	compteur := creerCompteur()
	fmt.Println(compteur(), compteur(), compteur()) // 1 2 3
}

func creerCompteur() func() int {
	n := 0
	return func() int {
		n++
		return n
	}
}

Résumé

  • Retourner (valeur, error) est LE pattern Go pour signaler un échec.
  • Les valeurs de retour nommées documentent le contrat de la fonction.
  • ...T accepte un nombre variable d'arguments, slice... les déballe.
  • Les fonctions sont des valeurs : elles se passent en paramètre, se retournent, capturent (closures).

Exercices pratiques

1 disponible
1

Mission : des validateurs qui contrôlent tous la mauvaise règle

Objectif : Corriger un bug classique de closures dans une boucle, et sécuriser l'usage d'une fonction retournant (valeur, error).

Contexte

Tu écris creerValidateurs(regles []int) []func(int) bool, une fonction qui doit construire une closure de validation par règle de la slice regles. En production, les tests montrent que TOUTES les closures retournées valident avec la même règle : la dernière de la liste, jamais les précédentes.

Résoudre l’exercice →