Le mot-clé type donne un nom à un type. Il existe sous deux formes. type UserID int64 est une définition de type, qui crée un tout nouveau type avec sa propre identité et son propre ensemble de méthodes. type Handler = http.Handler est une déclaration d'alias, qui ajoute un second nom à un type existant. La forme choisie détermine si le compilateur vous laisse mélanger les deux noms et si vous pouvez ajouter des méthodes (spécification Go).
En bref
type Name Underlyingdéfinit un nouveau type.type Name = Otherdéclare un alias. Un bloctype ( ... )regroupe plusieurs déclarations.- Un type défini est différent de tous les autres types, même de ceux qui ont le même type sous-jacent. Un
UserIDne peut pas être passé là où unOrderIDou unint64est attendu sans conversion explicite. - Les constantes non typées et les valeurs sans nom comme les littéraux de map s'affectent quand même à un type défini sans conversion, donc
cancelOrder(42)compile. - Un type défini démarre sans aucune méthode.
type Timestamp time.TimeperdFormat. Intégreztime.Timedans un struct (embedding) si vous voulez les conserver. - Vous ne pouvez déclarer des méthodes que sur des types de votre propre package.
func (d time.Duration) ...échoue aveccannot define new methods on non-local type. - Un alias est le même type sous un autre nom.
byte,runeetanysont des alias. Les alias acceptent des paramètres de type depuis Go 1.24. - Les types fonction comme
http.HandlerFuncpeuvent avoir des méthodes, et c'est ainsi que Go transforme une simple fonction en valeur d'interface. - Les types génériques prennent des paramètres de type, comme dans
Page[T any]. Depuis Go 1.27, les méthodes peuvent aussi déclarer leurs propres paramètres de type.
Comment déclarer un type en Go ?
Écrivez type, un nom et le type sur lequel il repose. Plusieurs déclarations peuvent partager un même bloc type ( ... ), comme les blocs const et var :
example.gogopackage main import ( "fmt" "net/http" "time" ) type ( UserID int64 OrderID int64 Order struct { ID OrderID UserID UserID CreatedAt time.Time } OrderStore interface { Get(id OrderID) (Order, error) } Handler = http.Handler ) func main() { o := Order{ID: 1001, UserID: 42} fmt.Println(o.ID) }
Ce programme affiche 1001. Le bloc contient quatre définitions de type et un alias. struct et interface sont les membres de droite les plus courants, et chacun a son propre mot-clé, présenté dans Les mots-clés de Go : les 25 expliqués. Les structs comme Order ont leur propre guide dans le mot-clé struct en Go. Les interfaces comme OrderStore ont leur propre guide dans le mot-clé interface en Go.
Les noms de types suivent les règles de visibilité habituelles. Order est exporté parce qu'il commence par une majuscule, alors que orderRow ne serait visible qu'à l'intérieur de son package. Le mot-clé package en Go détaille les noms exportés.
Vous pouvez aussi déclarer un type à l'intérieur d'une fonction. Il n'est alors visible que dans cette fonction, ce qui convient aux formes ponctuelles comme un corps de requête :
example.gogofunc main() { type loginRequest struct { Email string `json:"email"` Password string `json:"password"` } var req loginRequest err := json.Unmarshal([]byte(`{"email":"[email protected]","password":"hunter2"}`), &req) fmt.Println(req.Email, err) }
Ce programme affiche [email protected] <nil>. Un type local ne peut pas avoir de méthodes, car les déclarations de méthodes n'existent qu'au niveau du package.
Qu'est-ce qu'un type défini en Go ?
C'est un type déclaré sous la forme type Name Underlying, sans le =. La spécification appelle cela une définition de type. Chaque type défini a un type sous-jacent, qui détermine la façon dont ses valeurs sont stockées et les opérateurs qui s'y appliquent. UserID et OrderID ci-dessus ont tous deux le type sous-jacent int64, vous pouvez donc les additionner, les comparer et les afficher comme n'importe quel int64.
Ce qu'ils ne partagent pas, c'est leur identité. Un type défini est toujours différent de tous les autres types, et le compilateur refuse donc de les mélanger :
example.gogopackage main import "fmt" type UserID int64 type OrderID int64 func cancelOrder(id OrderID) { fmt.Println("cancelling order", id) } func main() { var user UserID = 42 cancelOrder(user) }
example.texttext./main.go:14:14: cannot use user (variable of int64 type UserID) as OrderID value in argument to cancelOrder
C'est cette erreur qui justifie de définir des types d'identifiants. Une fonction de signature refund(userID, orderID int64) accepte ses arguments dans n'importe quel ordre. Avec UserID et OrderID, les inverser provoque une erreur de compilation au lieu d'un remboursement envoyé au mauvais client.
La même règle sépare un type défini de son type sous-jacent. Passer un UserID à une fonction qui attend un int64 échoue aussi :
example.texttext./main.go:13:11: cannot use id (variable of int64 type UserID) as int64 value in argument to loadUser
Comment convertir entre types définis ?
Écrivez le type cible comme un appel de fonction. OrderID(user) et int64(user) compilent tous les deux, car une conversion est autorisée dès que deux types ont le même type sous-jacent. La conversion n'a aucun coût à l'exécution. La valeur reste identique, seul le type vu par le compilateur change.
Les conversions sont explicites à dessein. Quand vous voyez OrderID(user) en code review, vous savez que quelqu'un a décidé de traiter un identifiant d'utilisateur comme un identifiant de commande, et vous pouvez demander pourquoi.
Pourquoi pouvez-vous passer 42 ou un littéral de map sans conversion ?
Aucun des deux n'a encore de type défini. L'assignabilité obéit à quelques règles au-delà de « les types sont identiques », et deux d'entre elles reviennent constamment (spécification Go).
La première concerne les constantes non typées. 42 est une constante non typée, et elle peut devenir n'importe quel type dont le type sous-jacent peut la représenter. cancelOrder(42) compile donc et affiche cancelling order 42. C'est la même règle qui vous permet d'écrire 5 * time.Second, puisque time.Duration est lui-même un type défini dont le type sous-jacent est int64. Le mot-clé const en Go traite en détail des constantes non typées.
La seconde concerne les types sans nom. Une valeur peut être affectée à un type défini lorsque les deux ont des types sous-jacents identiques et qu'au moins l'un des deux n'est pas un type nommé. map[string]string est un littéral de type sans nom, donc une fonction qui prend un Headers l'accepte directement :
example.gogotype Headers map[string]string func send(h Headers) { fmt.Println(len(h), h["Content-Type"]) } func main() { send(map[string]string{"Content-Type": "application/json"}) raw := map[string]string{"Accept": "text/plain"} send(raw) }
Les deux appels compilent. La règle fonctionne de la même façon pour []byte, func(...) et les littéraux de struct. Elle ne permet jamais de passer directement d'un type nommé à un autre, comme de UserID à OrderID ou de UserID à int64.
Pourquoi un type défini n'hérite-t-il pas des méthodes ?
Un type défini reprend la structure du type sous-jacent, mais aucune de ses méthodes. Cela surprend la première fois que l'on encapsule time.Time :
example.gogopackage main import ( "fmt" "time" ) type Timestamp time.Time func main() { created := Timestamp(time.Now()) fmt.Println(created.Format(time.RFC3339)) }
example.texttext./main.go:12:22: created.Format undefined (type Timestamp has no field or method Format)
L'ensemble de méthodes démarre vide pour que le nouveau type puisse se comporter différemment de l'ancien. http.Header est un map[string][]string doté de méthodes comme Get et Set qui normalisent les noms d'en-têtes, et sort.StringSlice est un []string avec les méthodes dont sort.Interface a besoin. Si les types définis copiaient toutes les méthodes de leur type sous-jacent, Timestamp récupérerait des méthodes que vous n'avez jamais choisi de lui donner.
Vous avez deux options pour retrouver les anciennes méthodes. La première consiste à reconvertir vers le type d'origine là où vous en avez besoin. Une méthode String écrite ainsi contrôle la façon dont la valeur s'affiche :
example.gogotype Timestamp time.Time func (t Timestamp) String() string { return time.Time(t).Format(time.RFC3339) }
La seconde est l'embedding. Un struct qui intègre time.Time promeut toutes ses méthodes, et vous ajoutez les vôtres à côté :
example.gogopackage main import ( "fmt" "time" ) type Timestamp struct { time.Time } func (t Timestamp) ISO() string { return t.UTC().Format(time.RFC3339) } func main() { created := Timestamp{time.Date(2026, 9, 25, 14, 30, 0, 0, time.UTC)} fmt.Println(created.Format("2006-01-02")) fmt.Println(created.ISO()) }
Ce programme affiche 2026-09-25 puis 2026-09-25T14:30:00Z. L'embedding conserve Format, Before, Unix et toutes les autres méthodes de time.Time. Cela signifie aussi que Timestamp satisfait toutes les interfaces que satisfait time.Time, y compris json.Marshaler, et il s'encode donc en JSON sous forme de chaîne RFC 3339 plutôt que d'objet.
Pouvez-vous ajouter des méthodes à un type natif ou importé ?
Non. Une méthode doit être déclarée dans le même package que le type de son récepteur. Ajouter une méthode à time.Duration échoue :
example.gogofunc (d time.Duration) Minutes10() float64 { return d.Minutes() / 10 }
example.texttext./main.go:5:9: cannot define new methods on non-local type time.Duration
Il en va de même pour int, string et tous les autres types prédéclarés, ainsi que pour un alias vers un type d'un autre package. Cette règle empêche deux packages d'attacher des méthodes contradictoires au même type. Lorsque vous avez besoin d'un comportement supplémentaire, définissez votre propre type, comme type Cents int64, et placez-y les méthodes.
Qu'est-ce qu'un alias de type en Go ?
Un alias est un second nom pour un type qui existe déjà. Il s'écrit avec = :
example.gogotype Handler = http.Handler
Handler et http.Handler sont des types identiques. Vous pouvez passer l'un là où l'autre est attendu, le compilateur ne demande jamais de conversion et %T affiche le nom d'origine. Un alias ne crée pas non plus de nouvel ensemble de méthodes. Déclarer une méthode sur un alias revient à la déclarer sur le type d'origine, et la règle du « non-local type » s'y applique donc aussi.
Go utilise des alias pour ses propres noms prédéclarés. byte est un alias de uint8, rune de int32 et any de interface{}. C'est pourquoi un []byte peut être passé à une fonction qui attend un []uint8 sans conversion.
Les alias ont été ajoutés dans Go 1.9, principalement pour qu'un type puisse changer de package sans casser le code appelant. Quand une grosse base de code déplace Order de billing vers orders, l'ancien package conserve type Order = orders.Order pendant un temps. Le code qui importe encore billing compile, et les appelants migrent un par un (blog Go).
Un alias de type peut-il être générique ?
Oui, depuis Go 1.24. Un alias peut déclarer des paramètres de type, et il doit être instancié là où il est utilisé (notes de version de Go 1.24) :
example.gogotype Set[K comparable] = map[K]struct{} func main() { allowed := Set[string]{"GET": {}, "HEAD": {}} var plain map[string]struct{} = allowed _, ok := plain["GET"] fmt.Println(ok, len(allowed)) fmt.Printf("%T\n", allowed) }
Ce programme affiche true 2 puis map[string]struct {}. Set[string] n'est qu'un nom pour map[string]struct{}, donc l'affectation à plain ne nécessite aucune conversion.
Type défini vs alias de type : quelle différence ?
Type défini (type UserID int64) | Alias (type Handler = http.Handler) | |
|---|---|---|
| Nouveau type ? | Oui, différent de tous les autres types | Non, identique à l'original |
| Mélange avec l'original | Nécessite une conversion explicite | Fonctionne directement |
| Méthodes de l'original | Non héritées | Les mêmes méthodes, puisque c'est le même type |
| Peut déclarer de nouvelles méthodes | Oui, dans le même package | Seulement si l'original est un type défini de votre package |
%T affiche | main.UserID | Le nom d'origine |
| Usage typique | Identifiants, unités, enums, types métier | Déplacer des types entre packages, raccourcir des noms |
La plupart du temps, c'est un type défini qu'il vous faut. Tournez-vous vers un alias quand deux noms doivent désigner exactement le même type, généralement pendant un refactoring.
Comment fonctionnent les types fonction en Go ?
Une signature de fonction est un type comme un autre, vous pouvez donc la nommer avec type. Une fois nommée, elle peut avoir des méthodes. La bibliothèque standard s'en sert pour transformer une simple fonction en valeur d'interface. http.Handler est une interface avec une seule méthode, ServeHTTP, et une fonction n'est pas un Handler à elle seule :
example.gogofunc health(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "ok") } var h http.Handler = health
example.texttext./main.go:13:23: cannot use health (value of type func(w http.ResponseWriter, r *http.Request)) as http.Handler value in variable declaration: func(w http.ResponseWriter, r *http.Request) does not implement http.Handler (missing method ServeHTTP)
net/http déclare type HandlerFunc func(ResponseWriter, *Request) et lui donne une méthode ServeHTTP qui appelle la fonction elle-même. La conversion http.HandlerFunc(health) produit une valeur qui satisfait l'interface. http.HandleFunc et mux.HandleFunc effectuent cette conversion pour vous (documentation de net/http). Le mot-clé func en Go traite en détail des valeurs de fonction, des closures et des method values.
Le même motif fonctionne pour vos propres politiques. Une politique de retry est une fonction qui associe un délai au numéro de tentative, et une méthode sur le type fonction peut l'envelopper :
example.gogopackage main import ( "fmt" "time" ) type RetryPolicy func(attempt int) time.Duration func (p RetryPolicy) Capped(limit time.Duration) RetryPolicy { return func(attempt int) time.Duration { return min(p(attempt), limit) } } func Exponential(base time.Duration) RetryPolicy { return func(attempt int) time.Duration { return base << attempt } } func main() { policy := Exponential(100 * time.Millisecond).Capped(time.Second) for attempt := range 5 { fmt.Println(attempt, policy(attempt)) } }
example.texttext0 100ms 1 200ms 2 400ms 3 800ms 4 1s
Le nom indique au lecteur à quoi sert la fonction, et appeler Capped ressemble à l'appel d'une méthode sur n'importe quelle autre valeur. Un client HTTP peut prendre un champ RetryPolicy, et les tests peuvent passer une politique qui renvoie toujours 0.
Comment fonctionnent les types génériques en Go ?
Une déclaration de type peut avoir des paramètres de type entre crochets après le nom. Une réponse d'API paginée en est un cas courant, puisque les champs de pagination sont les mêmes quel que soit le type d'élément :
example.gogopackage main import ( "fmt" "strconv" ) type Page[T any] struct { Items []T NextCursor string } func (p Page[T]) HasMore() bool { return p.NextCursor != "" } func (p Page[T]) Map[U any](fn func(T) U) Page[U] { out := Page[U]{NextCursor: p.NextCursor} for _, item := range p.Items { out.Items = append(out.Items, fn(item)) } return out } type Order struct { ID int64 Total int64 } func main() { orders := Page[Order]{ Items: []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}}, NextCursor: "b3JkZXI6Mg", } ids := orders.Map(func(o Order) string { return strconv.FormatInt(o.ID, 10) }) fmt.Println(ids.Items, ids.HasMore()) }
Ce programme affiche [1 2] true. Page[Order] et Page[string] sont deux types différents construits à partir d'une seule déclaration. Les méthodes répètent le paramètre de type dans le récepteur, comme dans func (p Page[T]).
Map déclare son propre paramètre de type U. C'est une méthode générique, une nouveauté de Go 1.27 (notes de version de Go 1.27). Avec go 1.26 ou antérieur dans go.mod, le même code échoue avec :
example.texttext./main.go:17:22: generic method requires go1.27 or later (-lang was set to go1.26; check go.mod)
Les méthodes d'interface ne peuvent toujours pas avoir de paramètres de type. Map[U any](fn func(string) U) []U dans une interface échoue avec interface method must have no type parameters. Une méthode générique ne compte donc jamais pour satisfaire une interface.
Où le mot-clé type apparaît-il encore en Go ?
À quoi sert .(type) dans un type switch ?
x.(type) n'est valide que dans un switch, et il aiguille selon le type dynamique d'une valeur d'interface. Un usage courant concerne les erreurs :
example.gogofunc describe(err error) string { switch e := err.(type) { case nil: return "ok" case *fs.PathError: return "file error on " + e.Path case net.Error: return fmt.Sprintf("network error, timeout=%v", e.Timeout()) default: return "other: " + e.Error() } }
Dans chaque case, e a le type de ce case, donc e.Path et e.Timeout() compilent sans type assertion séparée. Appeler describe avec l'erreur renvoyée par os.Open("/etc/levelupgo.yaml") affiche file error on /etc/levelupgo.yaml quand le fichier n'existe pas.
Un type switch n'examine que l'erreur la plus externe. Après fmt.Errorf("load config: %w", err), le même appel tombe dans default. Pour les erreurs enveloppées, utilisez errors.As, qui parcourt la chaîne :
example.gogovar pathErr *fs.PathError if errors.As(wrapped, &pathErr) { fmt.Println("errors.As found", pathErr.Path) }
Comment utiliser type dans les contraintes génériques ?
Une contrainte est une interface, généralement déclarée avec type, qui peut lister des types en plus de méthodes. Le ~ devant un type signifie « tout type dont le type sous-jacent est celui-ci », et c'est ce qui laisse passer les types définis :
example.gogotype Cents int64 type Amount interface { ~int64 | ~float64 } func Sum[T Amount](values []T) T { var total T for _, v := range values { total += v } return total }
Sum([]Cents{4999, 1250}) renvoie 6249 sous forme de Cents. Sans le ~, Cents est rejeté, et le compilateur indique la correction :
example.texttext./main.go:20:17: Cents does not satisfy Amount (possibly missing ~ for int64 in Amount)
Les enums en Go reposent aussi sur des types définis. Un type comme type OrderStatus int accompagné d'un bloc const avec iota vous donne une liste de valeurs typées. Le mot-clé const en Go le détaille pas à pas, et le mot-clé var en Go explique comment les variables de ces types démarrent à leur valeur zéro.
La place de LevelUpGo
LevelUpGo enseigne Go au moyen d'exercices qui exécutent du vrai code Go dans le navigateur. Composite Types construit pas à pas des structs, des fonctions constructeurs et des méthodes. Interfaces & Polymorphism couvre les interfaces implicites, les type assertions et les type switches. Go Generics Masterclass aborde les types génériques et les contraintes personnalisées. Le Training Ground propose de courts exercices indépendants pour pratiquer en dehors d'un cours. Pour les 24 autres mots réservés, consultez Les mots-clés de Go : les 25 expliqués.
Questions fréquentes
type est-il un mot-clé en Go ?
Oui. type fait partie des 25 mots-clés réservés de Go, vous ne pouvez donc pas l'utiliser comme nom de variable ou de fonction. Il introduit une déclaration de type, soit une définition de type comme type UserID int64, soit un alias comme type Handler = http.Handler. Il apparaît aussi dans les type switches sous la forme x.(type).
Quelle est la différence entre une définition de type et un alias de type en Go ?
Une définition de type, type UserID int64, crée un nouveau type différent de int64 et qui démarre sans aucune méthode. Un alias, type ID = int64, est un autre nom pour int64 lui-même. Les valeurs d'un type défini nécessitent une conversion pour être mélangées avec le type d'origine, alors qu'un alias n'en nécessite aucune puisque les deux noms désignent le même type.
Pouvez-vous ajouter des méthodes à un type natif en Go ?
Non. Les méthodes ne peuvent être déclarées que sur des types définis dans le même package, donc func (n int) IsEven() bool échoue avec cannot define new methods on non-local type int. Définissez votre propre type, par exemple type Quantity int, et déclarez-y la méthode.
Pourquoi Go affiche-t-il "cannot use id (variable of int64 type UserID) as int64 value" ?
Parce que UserID est un type défini, et Go ne convertit jamais de lui-même une valeur typée d'un type nommé vers un autre, même lorsque le type sous-jacent est le même. Seules les constantes non typées comme 42 s'adaptent au type attendu. Convertissez explicitement avec int64(id). Si vous vous retrouvez à convertir en permanence, la fonction devrait probablement prendre un UserID à la place.
À quoi sert .(type) en Go ?
C'est la forme type switch d'une type assertion. switch e := err.(type) compare le type dynamique d'une valeur d'interface à chaque case, et à l'intérieur d'un case, e a le type de ce case. Cela ne fonctionne que dans un switch. Pour tester un seul type, utilisez v, ok := x.(T), et pour les erreurs enveloppées, utilisez errors.As.
Sources
- The Go Programming Language Specification, Type declarations : https://go.dev/ref/spec#Type_declarations
- The Go Programming Language Specification, Alias declarations : https://go.dev/ref/spec#Alias_declarations
- The Go Programming Language Specification, Assignability : https://go.dev/ref/spec#Assignability
- The Go Programming Language Specification, Method declarations : https://go.dev/ref/spec#Method_declarations
- The Go Programming Language Specification, Type switches : https://go.dev/ref/spec#Type_switches
- Go 1.24 Release Notes, alias de type génériques : https://go.dev/doc/go1.24
- Go 1.27 Release Notes : https://go.dev/doc/go1.27
- The Go Blog, What's in an (Alias) Name? : https://go.dev/blog/alias-names
- net/http, HandlerFunc : https://pkg.go.dev/net/http#HandlerFunc
