Déclarer une fonction
Une déclaration de fonction se compose de func, d'un nom, des paramètres, du type du résultat et d'un corps. Le type se place après le nom du paramètre, pas avant.
add prend deux valeurs int et renvoie un int. greet ne renvoie rien, donc sa signature n'a pas de type de résultat et son corps n'a pas besoin de return.
Une fonction qui déclare un résultat doit se terminer par une instruction terminale. Si un chemin peut atteindre l'accolade fermante sans retourner, le compilateur s'arrête avec missing return.
Des paramètres qui partagent un type
Des paramètres consécutifs du même type peuvent partager un seul nom de type. func add(a, b int) int signifie exactement la même chose que func add(a int, b int) int. Le mélange est aussi permis :
func scale(x, y float64, factor int) (float64, float64)
Ici x et y sont des float64 et factor est un int.
Valeurs de retour
Une fonction peut renvoyer zéro, une ou plusieurs valeurs. Plusieurs résultats se mettent entre parenthèses, et l'appelant les reçoit par une affectation multiple :
Ce code affiche 3 2. La paire (value, error) est l'usage le plus courant de cette fonctionnalité en Go, et elle a sa propre page sur les valeurs de retour multiples.
Les arguments sont copiés
Go passe chaque argument par valeur. La fonction travaille sur une copie, donc affecter une valeur à un paramètre ne modifie jamais la variable de l'appelant.
La sortie est :
{1 2}
{100 2}
[99 2 3]
La troisième ligne surprend. Une valeur slice est un petit en-tête (pointeur, longueur, capacité). La copie pointe vers le même tableau sous-jacent, donc écrire s[0] écrit l'élément de l'appelant. Mais append dans la fonction peut allouer un nouveau tableau, et l'appelant ne voit jamais ce nouvel en-tête. Si une fonction agrandit une slice, renvoyez la nouvelle slice. La même logique s'applique aux maps : la fonction peut ajouter des clés que l'appelant voit, parce que la valeur map fait référence à des données partagées.
Utilisez un paramètre pointeur quand la fonction doit modifier la variable de l'appelant, ou quand la valeur est une grosse struct que vous ne voulez pas copier à chaque appel.
Les fonctions sont des valeurs
Une fonction a un type, écrit sans le nom : func(int, int) int. Vous pouvez stocker une fonction dans une variable, la mettre dans une map ou une slice, et la passer à une autre fonction.
square est une fonction anonyme affectée à une variable. Les fonctions anonymes peuvent aussi lire et modifier des variables de la portée environnante, ce qui en fait des closures.
Un type fonction nommé rend les signatures plus lisibles quand la même forme apparaît à de nombreux endroits :
type Transform func(int) int
func apply(xs []int, f Transform) []int
La valeur zéro d'un type fonction est nil. Appeler une fonction nil provoque un panic invalid memory address or nil pointer dereference, donc vérifiez les callbacks optionnels avant de les appeler. Les valeurs fonction ne peuvent être comparées qu'à nil, jamais entre elles.
Récursion
Une fonction peut s'appeler elle-même. Go n'a pas d'optimisation des appels terminaux, mais les piles des goroutines grandissent à la demande, donc des profondeurs de récursion ordinaires ne posent pas de problème.
for i := range 6 compte de 0 à 5 et demande Go 1.22 ou plus.
Ce que les fonctions Go n'ont pas
- Pas de surcharge. Deux fonctions d'un même package ne peuvent pas partager un nom, même avec des paramètres différents.
- Pas d'arguments par défaut ni nommés. Chaque paramètre est passé à chaque appel, dans l'ordre. Une struct de configuration vous donne des champs nommés et facultatifs :
NewServer(Config{Port: 8080}). - Pas de fonctions nommées imbriquées. Dans le corps d'une fonction, vous ne pouvez déclarer que des fonctions anonymes, généralement affectées à une variable.
Les listes d'arguments de longueur variable sont prises en charge par les fonctions variadiques.
Fonctions exportées et non exportées
Une fonction dont le nom commence par une majuscule (Parse) est exportée et peut être appelée depuis d'autres packages. Un nom en minuscule (parse) n'est visible que dans son propre package. Il n'y a pas de mots-clés public ou private ; la casse est toute la règle.
Erreurs courantes
- S'attendre à ce que la modification d'un paramètre atteigne l'appelant. Affecter une valeur à un paramètre ne modifie que la copie. Renvoyez la nouvelle valeur ou prenez un pointeur.
- Faire un append dans une fonction et ignorer le résultat.
func add(s []int) { s = append(s, 1) }n'a aucun effet sur la longueur de la slice de l'appelant. Écrivezfunc add(s []int) []intet utilisezs = add(s). - Des résultats ignorés. Go permet d'ignorer une valeur de retour.
go vetne signale pas une erreur ignorée, mais des linters commeerrcheck(inclus dansgolangci-lint) le font. N'utilisez_ =que lorsqu'ignorer est une décision.
Questions fréquentes
Comment définir une fonction en Go ?
Utilisez le mot-clé func, un nom, une liste de paramètres avec les types après les noms, et le type du résultat : func add(a, b int) int { return a + b }. Une fonction sans résultat omet le type, et une fonction avec plusieurs résultats les liste entre parenthèses : func divmod(a, b int) (int, int).
Go passe-t-il les arguments par valeur ou par référence ?
Toujours par valeur. La fonction reçoit une copie de chaque argument. Pour un int ou une struct, cette copie est indépendante de la variable de l'appelant. Les slices, maps, channels et pointeurs sont de petites valeurs qui font référence à des données partagées, donc les modifications des éléments pointés sont visibles par l'appelant, alors que réaffecter le paramètre lui-même ne l'est pas. Passez un pointeur (*T) quand la fonction doit modifier la variable de l'appelant.
Peut-on passer une fonction en paramètre en Go ?
Oui. Les fonctions sont des valeurs avec un type comme func(int) int. Déclarez un paramètre de ce type et passez n'importe quelle fonction de signature compatible, nommée ou anonyme : func apply(xs []int, f func(int) int).
Go supporte-t-il la surcharge de fonctions ou les paramètres par défaut ?
Non. Chaque nom de fonction d'un package doit être unique, et chaque paramètre doit être passé à chaque appel. Les substituts habituels sont des noms distincts (NewServer, NewServerWithTLS), une struct de configuration, les paramètres variadiques, ou les options fonctionnelles.