Le mot-clé func déclare tout comportement d'un programme Go. func parse(body []byte) (Request, error) est une fonction. func (s *Server) Start() error est une méthode, car elle possède un receiver avant son nom. func(w http.ResponseWriter, r *http.Request) { ... }, sans nom, est un littéral de fonction : il peut capturer les variables qui l'entourent, et c'est ainsi que Go écrit les closures. Un seul mot-clé couvre les trois cas, et les règles des paramètres et des résultats sont les mêmes pour chacun (spécification Go).
En bref
- Une déclaration de fonction s'écrit
func name(params) results { body }. Des paramètres consécutifs de même type partagent ce type, comme dansclamp(n, lo, hi int). - Go n'a ni surcharge ni arguments par défaut. Deux fonctions portant le même nom échouent avec
redeclared in this block. Utilisez un autre nom, un struct d'options ou des functional options. - Une fonction peut renvoyer plusieurs valeurs. La paire
(T, error)est la façon dont Go signale un échec, et_ignore un résultat dont vous n'avez pas besoin. - Les résultats nommés documentent ce que renvoie une fonction et permettent à une closure différée d'annoter
errà la sortie. ...Trend une fonction variadique. À l'intérieur, le paramètre est un[]T, etparts...déplie un slice dans l'appel.- Une méthode est une fonction dotée d'un receiver. Utilisez un receiver pointeur quand la méthode modifie la valeur ou quand le type contient un
sync.Mutex. - Une méthode à receiver pointeur ne fait pas partie du method set du type valeur, donc
API{}ne satisfait pashttp.Handlerquand seul*APIpossèdeServeHTTP. - Les fonctions sont des valeurs. Vous pouvez les stocker, les passer à
slices.SortFuncouhttp.HandleFunc, et ne les comparer qu'ànil. - Un littéral de fonction capture les variables elles-mêmes, pas des copies. Depuis Go 1.22, chaque itération de boucle reçoit sa propre variable de boucle, donc les goroutines lancées dans une boucle voient la valeur attendue.
- Les functional options (
NewServer(addr, opts ...Option)) donnent des réglages optionnels aux constructeurs. Depuis Go 1.27, les méthodes peuvent déclarer leurs propres paramètres de type, comme dansfunc (c *Client) GetJSON[T any](path string) (T, error).
Comment déclarer une fonction en Go ?
Écrivez func, un nom, la liste des paramètres et les types des résultats. Un parseur de token est une petite fonction typique :
example.gogopackage main import ( "errors" "fmt" "strings" ) func parseBearer(header string) (string, error) { token, ok := strings.CutPrefix(header, "Bearer ") if !ok || token == "" { return "", errors.New("missing bearer token") } return token, nil } func clamp(n, lo, hi int) int { return max(lo, min(n, hi)) } func main() { token, err := parseBearer("Bearer eyJhbGciOi") fmt.Println(token, err) _, err = parseBearer("Basic dXNlcjpwYXNz") fmt.Println(err) fmt.Println(clamp(250, 1, 100)) }
example.texttexteyJhbGciOi <nil> missing bearer token 100
Les types viennent après les noms, pour les paramètres comme pour les résultats. Quand plusieurs paramètres consécutifs partagent un type, vous ne l'écrivez qu'une fois : clamp(n, lo, hi int) prend donc trois int. Un résultat unique et non nommé n'a pas besoin de parenthèses. Deux résultats ou plus en exigent.
Les noms de fonctions suivent la même règle de visibilité que tout autre identifiant Go. ParseBearer serait exporté et appelable depuis d'autres packages, alors que parseBearer reste dans le sien. Le mot-clé package en Go détaille les noms exportés.
Go permet-il la surcharge de fonctions ou les arguments par défaut ?
Non, dans les deux cas. Un package ne peut contenir qu'une seule fonction portant un nom donné, quels que soient ses paramètres :
example.gogofunc dial(addr string) error { return nil } func dial(addr string, timeout time.Duration) error { return nil }
example.texttext./main.go:7:6: dial redeclared in this block ./main.go:5:6: other declaration of dial
Les valeurs par défaut échouent dès le parseur, avant même le début de la vérification des types :
example.texttext./main.go:5:46: syntax error: unexpected = in parameter list; possibly missing comma or )
La bibliothèque standard montre ce que Go fait à la place. net.Dial et net.DialTimeout sont deux noms pour deux comportements. http.Server est un struct dont les champs à zéro signifient « utiliser la valeur par défaut », si bien que vous ne renseignez que les champs qui vous intéressent. Pour les constructeurs dotés de nombreux réglages optionnels, le code Go utilise des functional options, présentées plus bas. Le nom employé à chaque appel indique quelle fonction s'exécute.
Comment fonctionnent les valeurs de retour multiples en Go ?
Une fonction peut renvoyer un nombre quelconque de valeurs, que l'appelant reçoit par une affectation multiple. La forme la plus courante est un résultat accompagné d'une error, comme dans parseBearer plus haut. L'appelant vérifie l'erreur avant de toucher au résultat, et le compilateur s'assure qu'un appel à deux résultats n'est pas utilisé là où une seule valeur est attendue.
Quand vous n'avez pas besoin de l'une des valeurs, affectez-la à l'identifiant vide _. Vous ne pouvez pas l'omettre complètement. token := parseBearer(h) échoue avec assignment mismatch: 1 variable but parseBearer returns 2 values.
Quand utiliser des résultats nommés ?
Les résultats nommés donnent un nom à chaque résultat et le déclarent comme une variable initialisée à sa valeur zéro. Un return sans valeur, appelé bare return, renvoie alors ce que contiennent ces variables :
example.gogofunc splitHostPort(addr string) (host, port string) { for i := len(addr) - 1; i >= 0; i-- { if addr[i] == ':' { host, port = addr[:i], addr[i+1:] return } } host = addr return }
splitHostPort("api.levelupgo.dev:443") renvoie api.levelupgo.dev et 443. Les noms sont utiles ici, car deux résultats string ne diraient rien sinon sur lequel est lequel. Les Go Code Review Comments ne conseillent les bare returns que dans des fonctions courtes comme celle-ci. Dans une fonction de 60 lignes, un return nu masque ce qui est renvoyé.
Les résultats nommés comptent surtout quand une fonction différée doit modifier l'erreur avant que l'appelant ne la voie :
example.gogofunc loadConfig(path string) (cfg Config, err error) { defer func() { if err != nil { err = fmt.Errorf("load config %s: %w", path, err) } }() data, err := os.ReadFile(path) if err != nil { return Config{}, err } err = json.Unmarshal(data, &cfg) return cfg, err }
example.texttextload config /etc/levelupgo/config.json: open /etc/levelupgo/config.json: no such file or directory
Chaque return affecte d'abord ses valeurs à cfg et err, puis la fonction différée s'exécute. Comme err est un résultat nommé, la closure peut l'envelopper une seule fois pour tous les chemins de sortie. Sans ce nom, la fonction différée n'a aucun moyen d'atteindre la valeur renvoyée.
Qu'est-ce qu'une fonction variadique en Go ?
Un dernier paramètre écrit ...T accepte zéro ou plusieurs arguments de type T. Dans la fonction, c'est un simple []T :
example.gogofunc buildURL(base string, segments ...string) string { return strings.TrimSuffix(base, "/") + "/" + strings.Join(segments, "/") } func main() { fmt.Println(buildURL("https://api.levelupgo.dev/", "v1", "users", "42")) fmt.Println(buildURL("https://api.levelupgo.dev")) parts := []string{"v1", "orders", "1001"} fmt.Println(buildURL("https://api.levelupgo.dev", parts...)) }
example.texttexthttps://api.levelupgo.dev/v1/users/42 https://api.levelupgo.dev/ https://api.levelupgo.dev/v1/orders/1001
parts... passe un slice existant comme argument variadique sans le copier. Vous ne pouvez pas mélanger les deux formes, donc buildURL(base, "v2", parts...) échoue :
example.texttext./main.go:18:58: too many arguments in call to buildURL have (string, string, []string...) want (string, ...string)
La bibliothèque standard en regorge. fmt.Println(a ...any), append(s, elems...), errors.Join(errs ...error) et les paires clé-valeur de slog.Info(msg, args ...any) prennent toutes cette forme.
Comment fonctionnent les méthodes en Go ?
Une méthode est une fonction dotée d'un receiver, écrit entre parenthèses entre func et le nom. Les receivers sont le plus souvent des structs, que le mot-clé struct en Go couvre des champs jusqu'à l'embedding. Le receiver peut être le type lui-même ou un pointeur vers ce type, et ce choix détermine si la méthode agit sur la valeur de l'appelant ou sur une copie. Un rate limiter avec un receiver valeur montre la différence :
example.gogotype RateLimiter struct { limit int used int } func (r RateLimiter) Allow() bool { if r.used >= r.limit { return false } r.used++ return true } func main() { limiter := RateLimiter{limit: 2} for range 4 { fmt.Print(limiter.Allow(), " ") } fmt.Println() }
example.texttexttrue true true true
Ce limiter ne bloque jamais rien. Chaque appel reçoit une nouvelle copie de limiter, incrémente used sur cette copie puis la jette. Passer le receiver à func (r *RateLimiter) Allow() bool fait modifier l'original par la méthode, et la sortie devient true true false false. Vous n'avez pas besoin d'écrire (&limiter).Allow(). Go prend l'adresse pour vous quand la variable est adressable.
Quand utiliser un receiver pointeur ?
Utilisez un receiver pointeur dès que l'un de ces cas s'applique :
- La méthode modifie le receiver, comme
Allowci-dessus. - Le type contient un
sync.Mutexou une autre valeur qui ne doit pas être copiée. - Le struct est volumineux, et le copier à chaque appel coûte plus cher que de suivre un pointeur.
Receiver valeur func (r T) | Receiver pointeur func (r *T) | |
|---|---|---|
| Modifie la valeur de l'appelant | Non, il agit sur une copie | Oui |
Sûr avec un champ sync.Mutex | Non, le verrou est copié | Oui |
Appelable sur une variable T | Oui | Oui, Go prend l'adresse |
Dans le method set de T | Oui | Non |
Dans le method set de *T | Oui | Oui |
Le cas du mutex est un bug que go vet détecte :
example.gogotype SessionCache struct { mu sync.Mutex sessions map[string]string } func (c SessionCache) Get(token string) (string, bool) { c.mu.Lock() defer c.mu.Unlock() user, ok := c.sessions[token] return user, ok }
example.texttextmain.go:13:9: Get passes lock by value: app.SessionCache contains sync.Mutex
Chaque appel verrouille sa propre copie du mutex, donc deux goroutines qui appellent Get ne s'attendent jamais l'une l'autre et le verrou ne protège rien. Les petits types immuables comme time.Time ou un UserID se contentent très bien d'un receiver valeur. Les Code Review Comments demandent aussi de ne pas mélanger les deux sur un même type. Si une méthode a besoin d'un receiver pointeur, donnez-en un à toutes (wiki Go).
Pourquoi mon type n'implémente-t-il pas une interface ?
Une cause fréquente est un receiver pointeur. Le method set d'un type valeur T ne contient que les méthodes à receiver T. Le method set de *T contient les deux sortes. Donc, quand ServeHTTP a un receiver pointeur, seul *API est un http.Handler :
example.gogotype API struct { version string } func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, a.version) } func main() { var h http.Handler = API{version: "v1"} _ = h }
example.texttext./main.go:17:23: cannot use API{…} (value of struct type API) as http.Handler value in variable declaration: API does not implement http.Handler (method ServeHTTP has pointer receiver)
Écrire &API{version: "v1"} corrige le problème. La règle existe parce qu'une interface peut contenir une copie d'une valeur, et une méthode pointeur appelée sur cette copie modifierait quelque chose que l'appelant ne voit jamais. Le mot-clé var en Go montre le pattern var _ http.Handler = (*T)(nil) qui vérifie cela à la compilation, et le mot-clé interface en Go traite en profondeur les method sets et la satisfaction implicite.
Que sont les valeurs de méthode et les expressions de méthode ?
srv.health sans parenthèses est une valeur de méthode (method value). C'est une fonction dont le receiver est déjà lié, que vous pouvez donc transmettre directement à tout ce qui attend une fonction ordinaire :
example.gogotype Server struct { version string } func (s *Server) health(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "ok %s\n", s.version) } func main() { srv := &Server{version: "1.4.2"} mux := http.NewServeMux() mux.HandleFunc("GET /health", srv.health) rec := httptest.NewRecorder() mux.ServeHTTP(rec, httptest.NewRequest("GET", "/health", nil)) fmt.Print(rec.Body.String()) handle := (*Server).health fmt.Printf("%T\n", handle) }
example.texttextok 1.4.2 func(*main.Server, http.ResponseWriter, *http.Request)
Beaucoup de serveurs Go enregistrent leurs routes de cette façon. Les handlers sont des méthodes d'un struct qui contient la base de données, le logger et la configuration, et mux.HandleFunc reçoit les valeurs de méthode liées. (*Server).health est une expression de méthode (method expression). Elle transforme le receiver en premier paramètre ordinaire, ce qui est utile quand une table choisit à l'exécution la méthode à appeler.
Que sont les valeurs de fonction et les types fonction ?
En Go, les fonctions sont des valeurs. Vous pouvez les affecter à des variables, les stocker dans des champs de struct et des maps, les passer en argument et les renvoyer depuis d'autres fonctions. Le type d'une valeur de fonction est sa signature :
example.gogotype Order struct { ID int64 Total int64 } func main() { orders := []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}, {ID: 3, Total: 8900}} slices.SortFunc(orders, func(a, b Order) int { return cmp.Compare(b.Total, a.Total) }) fmt.Println(orders) bigOrder := func(o Order) bool { return o.Total > 5000 } fmt.Println(slices.IndexFunc(orders, bigOrder)) fmt.Printf("%T\n", bigOrder) }
example.texttext[{3 8900} {1 4999} {2 1250}] 0 func(main.Order) bool
slices.SortFunc ne sait pas comment ordonner vos structs, vous lui passez donc une fonction de comparaison. La même idée traverse toute la bibliothèque standard : http.HandleFunc prend une fonction handler, strings.FieldsFunc prend un test de séparateur et sync.OnceValue prend la fonction à exécuter une seule fois.
La valeur zéro d'un type fonction est nil, et appeler une fonction nil provoque un panic. C'est aussi la seule comparaison que Go autorise :
example.texttext./main.go:9:5: invalid operation: health == ready (func can only be compared to nil)
Comme vous ne pouvez pas comparer deux fonctions, elles ne peuvent pas non plus servir de clés de map. Quand vous voulez un ensemble de handlers, indexez plutôt la map par nom.
Une signature peut aussi recevoir son propre nom avec type, et un type fonction nommé peut avoir des méthodes. C'est ainsi que http.HandlerFunc transforme une simple fonction en http.Handler, et le mot-clé type en Go le détaille avec un exemple de RetryPolicy.
Comment fonctionnent les closures en Go ?
Un littéral de fonction peut utiliser les variables de la fonction qui l'entoure. Il ne les copie pas. Il capture les variables elles-mêmes, donc une modification d'un côté est visible des deux. Un helper de retry le montre :
example.gogovar errUnavailable = errors.New("503 service unavailable") func retry(attempts int, fn func() error) error { var err error for range attempts { if err = fn(); err == nil { return nil } } return err } func main() { calls := 0 err := retry(3, func() error { calls++ return errUnavailable }) fmt.Println(calls, err) }
example.texttext3 503 service unavailable
calls vit dans main, mais le littéral l'incrémente, et main voit 3 ensuite. La variable capturée reste en vie tant qu'une closure y fait référence, même après le retour de la fonction englobante. Le compilateur déplace ces variables sur le tas quand c'est nécessaire.
Les middlewares HTTP comptent parmi les closures les plus courantes du code Go. Le handler renvoyé conserve logger et next de l'appel qui l'a construit :
example.gogofunc logRequests(logger *slog.Logger, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) logger.Info("request", "method", r.Method, "path", r.URL.Path) }) }
Envelopper un handler et y faire passer DELETE /sessions/42 produit le log level=INFO msg=request method=DELETE path=/sessions/42. Chaque appel à logRequests crée une closure distincte avec son propre next, vous pouvez donc envelopper chaque route avec un handler différent. Pour la pile complète de production construite à partir de closures comme celle-ci, ordre d'exécution, récupération des panics et tests compris, voir les bonnes pratiques des middlewares Go.
Vous croiserez aussi souvent deux formes courtes. defer func() { ... }() exécute une closure au retour de la fonction : c'est ainsi que l'exemple loadConfig annote son erreur et que recover est appelé. go func() { ... }() lance une closure dans une nouvelle goroutine. Les () finales appellent le littéral. Si vous les oubliez, le compilateur signale que l'expression passée à defer ou go doit être un appel de fonction.
Go 1.22 a-t-il corrigé les closures dans les boucles ?
Oui. Avant Go 1.22, une boucle for avait une seule variable de boucle partagée par toutes les itérations, et les closures capturaient cette unique variable. Ce health checker lançait trois goroutines qui affichaient souvent toutes la dernière URL :
example.gogourls := []string{"/health", "/ready", "/metrics"} var wg sync.WaitGroup for _, url := range urls { wg.Add(1) go func() { defer wg.Done() fmt.Println("checking", url) }() } wg.Wait()
Avec go 1.21 dans go.mod, chaque ligne de trois exécutions distinctes affichait checking /metrics, et go vet avertissait loop variable url captured by func literal. Avec go 1.22 ou plus récent, chaque itération a son propre url, et le même code vérifie les trois chemins dans l'ordre où les goroutines se terminent (blog Go). Le changement dépend de la ligne go de go.mod, donc un ancien module garde l'ancien comportement tant que vous ne la relevez pas. L'ancienne copie url := url dans la boucle n'est plus nécessaire, et le modernizer forvar de go fix la supprime. Le mot-clé for en Go couvre les autres règles des variables de boucle, par exemple pourquoi modifier la valeur du range ne modifie pas le slice.
Que sont les functional options en Go ?
Les functional options combinent paramètres variadiques et closures pour donner à un constructeur des réglages optionnels, sans surcharge ni arguments par défaut. Chaque option est une fonction qui modifie la valeur en cours de construction :
example.gogotype Server struct { addr string readTimeout time.Duration maxBodyBytes int64 logger *slog.Logger } type Option func(*Server) func WithReadTimeout(d time.Duration) Option { return func(s *Server) { s.readTimeout = d } } func WithMaxBodyBytes(n int64) Option { return func(s *Server) { s.maxBodyBytes = n } } func NewServer(addr string, opts ...Option) *Server { s := &Server{ addr: addr, readTimeout: 5 * time.Second, maxBodyBytes: 1 << 20, logger: slog.Default(), } for _, opt := range opts { opt(s) } return s } func main() { a := NewServer(":8080") b := NewServer(":8080", WithReadTimeout(30*time.Second), WithMaxBodyBytes(10<<20)) fmt.Println(a.readTimeout, a.maxBodyBytes) fmt.Println(b.readTimeout, b.maxBodyBytes) }
example.texttext5s 1048576 30s 10485760
NewServer(":8080") reçoit toutes les valeurs par défaut, et les appelants ne nomment que ce qu'ils changent. Ajouter une option plus tard ne casse aucun appel existant. grpc.NewServer(opts ...ServerOption) de gRPC et de nombreux clients de base de données utilisent ce pattern. Pour un type à deux ou trois réglages, un simple struct de configuration est plus simple et plus lisible : réservez les options aux cas où la liste des réglages ne cesse de s'allonger.
Comment fonctionnent les fonctions génériques en Go ?
Une fonction peut déclarer des paramètres de type entre crochets, entre son nom et ses paramètres. Map fonctionne pour n'importe quels types d'entrée et de sortie :
example.gogofunc Map[T, U any](items []T, fn func(T) U) []U { out := make([]U, 0, len(items)) for _, item := range items { out = append(out, fn(item)) } return out } emails := Map(users, func(u User) string { return u.Email })
Le compilateur infère T comme User et U comme string à partir des arguments, donc l'appel n'a pas besoin de crochets. Les packages slices, maps et cmp de la bibliothèque standard sont construits à partir de fonctions génériques de ce type.
Jusqu'à Go 1.27, les méthodes ne pouvaient pas déclarer leurs propres paramètres de type. Elles pouvaient utiliser ceux de leur type receiver, et rien de plus. Go 1.27 lève cette limite, si bien qu'un client peut avoir une méthode générique qui décode vers le type demandé par l'appelant (notes de version de Go 1.27) :
example.gogotype Client struct { baseURL string http *http.Client } func (c *Client) GetJSON[T any](path string) (T, error) { var out T resp, err := c.http.Get(c.baseURL + path) if err != nil { return out, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&out) return out, err } u, err := c.GetJSON[User]("/users/1")
Face à un serveur de test qui renvoie un utilisateur, ce code affiche {1 [email protected]} <nil>. L'argument de type doit être écrit explicitement ici, car rien dans "/users/1" n'indique au compilateur ce qu'est T. Les nouveautés de Go 1.27 présente les méthodes génériques et leurs limites, notamment pourquoi elles ne peuvent pas satisfaire des méthodes d'interface.
À quoi servent main et init en Go ?
Deux noms de fonction sont particuliers. func main() dans package main est le point de départ d'un programme, et le programme se termine quand elle retourne. Elle ne prend aucun argument et ne renvoie rien :
example.texttext./main.go:9:6: func main must have no arguments and no return values
Les arguments de ligne de commande proviennent de os.Args ou du package flag, et le code de sortie de os.Exit. Le mot-clé package en Go traite package main en profondeur.
func init() s'exécute automatiquement après l'initialisation des variables du package et avant main. Un package peut avoir plusieurs fonctions init, même dans un seul fichier, et elles s'exécutent dans leur ordre d'apparition. Vous ne pouvez pas en appeler une vous-même. init() dans votre propre code échoue avec undefined: init. La plupart des usages de init servent à l'enregistrement, par exemple un driver de base de données qui s'ajoute à database/sql. Le mot-clé var en Go explique l'ordre d'initialisation et quand un var au niveau du package est le choix le plus clair.
La place de LevelUpGo
LevelUpGo enseigne Go par des exercices qui exécutent du vrai code Go dans le navigateur. Go Basics présente les fonctions, les valeurs de retour multiples et les erreurs en tant que valeurs. Go Language Deep Dives va plus loin avec les fonctions variadiques, les résultats nommés, panic et recover. Le Training Ground propose de courts exercices indépendants sur les signatures, les méthodes et les closures pour pratiquer en dehors d'un cours. Pour les 24 autres mots réservés, voir les mots-clés Go : les 25 expliqués.
Questions fréquentes
func est-il un mot-clé en Go ?
Oui. func fait partie des 25 mots-clés réservés de Go, vous ne pouvez donc pas l'utiliser comme nom. Il déclare les fonctions et les méthodes, et introduit les littéraux de fonction et les types fonction. Le code Go qui a besoin d'une variable contenant une fonction la nomme généralement fn.
Go prend-il en charge la surcharge de fonctions ou les paramètres par défaut ?
Non, dans les deux cas. Chaque nom ne peut être déclaré qu'une fois par package, donc un second func dial(...) échoue avec dial redeclared in this block, et une valeur par défaut dans une liste de paramètres est une erreur de syntaxe. Go utilise des noms distincts, comme net.Dial et net.DialTimeout, un struct de configuration dont les champs à zéro signifient « utiliser la valeur par défaut », comme http.Server, ou des functional options.
Faut-il un receiver valeur ou pointeur en Go ?
Utilisez un receiver pointeur quand la méthode modifie le receiver, quand le type contient un sync.Mutex ou équivalent, ou quand le struct est volumineux. Utilisez un receiver valeur pour les petits types qui se comportent comme des valeurs, comme time.Time. Restez cohérent sur toutes les méthodes d'un même type. N'oubliez pas que seul *T possède les méthodes pointeur dans son method set, ce qui compte pour la satisfaction des interfaces.
Qu'est-ce que func() en Go ?
func() est le type d'une fonction qui ne prend aucun argument et ne renvoie rien. Toute signature écrite sans nom est un type fonction, comme func(context.Context) error. Vous utilisez ces types pour les callbacks, les champs de struct et des paramètres comme sync.OnceFunc(f func()). func() { ... } avec un corps est un littéral de fonction de ce type.
Peut-on comparer des fonctions en Go ?
Uniquement à nil. health == ready échoue avec func can only be compared to nil, et pour la même raison un type fonction ne peut pas être une clé de map. Quand vous devez retrouver des handlers, stockez-les dans une map[string]http.HandlerFunc indexée par nom.
Sources
- The Go Programming Language Specification, Function declarations : https://go.dev/ref/spec#Function_declarations
- The Go Programming Language Specification, Method declarations : https://go.dev/ref/spec#Method_declarations
- The Go Programming Language Specification, Method sets : https://go.dev/ref/spec#Method_sets
- The Go Programming Language Specification, Function literals : https://go.dev/ref/spec#Function_literals
- The Go Programming Language Specification, Passing arguments to ... parameters : https://go.dev/ref/spec#Passing_arguments_to_..._parameters
- Effective Go, Functions : https://go.dev/doc/effective_go#functions
- Go Code Review Comments, Named result parameters et Receiver type : https://go.dev/wiki/CodeReviewComments
- The Go Blog, Fixing For Loops in Go 1.22 : https://go.dev/blog/loopvar-preview
- Notes de version de Go 1.27 : https://go.dev/doc/go1.27
