
Les structs en Go regroupent plusieurs valeurs de types différents sous un même type personnalisé. Elles remplacent les objets et les classes des langages orientés objet : Go n'a pas d'héritage, il modélise les données avec des structs et attache le comportement via des méthodes. C'est la brique de base pour représenter une entité métier, un message ou une configuration. Public visé : débutant à intermédiaire en Go.
Ce guide part de la déclaration pour aller jusqu'aux idiomes qui font la différence en vrai : tags JSON, composition par embedding et comparaison. Tous les exemples sont testés avec Go.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Déclarer une struct et l'initialiser de plusieurs façons.
- Exposer des champs et les sérialiser en JSON avec des tags.
- Composer des types par embedding plutôt que par héritage.
- Comparer des structs et connaître leurs pièges.
Déclarer une struct
Section intitulée « Déclarer une struct »La déclaration combine les mots-clés type et struct. Chaque champ porte un nom et un type.
type Personne struct { nom string age int actif bool}Ce nouveau type Personne s'utilise ensuite comme n'importe quel autre type : variable, paramètre, valeur de retour, élément de slice ou de map.
Initialiser une struct
Section intitulée « Initialiser une struct »Go propose plusieurs formes d'initialisation. La plus lisible et la plus robuste est celle par nom de champ.
// Positionnel : l'ordre doit suivre la déclaration (fragile)p1 := Personne{"Alice", 30, true}
// Par nom de champ : lisible, résistant aux réordonnancementsp2 := Personne{ nom: "Bob", age: 25,}
// Partiel : les champs absents prennent leur valeur zérop3 := Personne{nom: "Charlie"}
// Pointeur vers une nouvelle instancep4 := &Personne{nom: "Diana", age: 35}Accéder et modifier les champs
Section intitulée « Accéder et modifier les champs »L'accès se fait avec l'opérateur point. Sur un pointeur, le déréférencement est automatique : pas besoin d'écrire (*p4).nom.
p2.age = 26 // modificationfmt.Println(p2.nom) // lecture : Bob
p4.nom = "Diana Smith" // via pointeur, déréférencement impliciteUne fonction constructeur
Section intitulée « Une fonction constructeur »Go n'a pas de constructeur intégré. La convention est une fonction NewXxx (ou NouvelleXxx) qui encapsule la logique d'initialisation et applique des valeurs par défaut.
func NouvellePersonne(nom string, age int) *Personne { return &Personne{ nom: nom, age: age, actif: true, // défaut métier }}Retourner un pointeur est courant quand la struct est modifiée après création ou qu'elle est volumineuse.
Champs exportés et tags JSON
Section intitulée « Champs exportés et tags JSON »La visibilité d'un champ dépend de la casse de son initiale : une majuscule rend le champ exporté (public, vu hors du package et par les bibliothèques), une minuscule le garde privé au package. Cette règle est capitale pour la sérialisation : encoding/json n'exporte que les champs à initiale majuscule.
Les tags ajustent ce comportement sans changer le nom Go du champ.
type User struct { Name string `json:"name"` Email string `json:"email,omitempty"` // absent du JSON si vide admin bool // non exporté : ignoré par json}
u := User{Name: "Bob", admin: true}b, _ := json.Marshal(u)fmt.Println(string(b))// {"name":"Bob"}Le champ admin disparaît (non exporté) et email aussi grâce à omitempty puisqu'il est vide. Les tags servent de la même façon à d'autres bibliothèques (validation, base de données, YAML).
Composer par embedding plutôt qu'hériter
Section intitulée « Composer par embedding plutôt qu'hériter »Go remplace l'héritage par la composition. Un champ anonyme (un type sans nom de champ) est embarqué : ses champs et méthodes sont promus dans le type englobant, comme s'ils lui appartenaient.
type Adresse struct { Ville string CP string}
type Client struct { Nom string Adresse // embedding : champ anonyme}
c := Client{Nom: "ACME", Adresse: Adresse{Ville: "Paris", CP: "75001"}}fmt.Println(c.Ville) // Paris <- champ promu, accès directfmt.Println(c.Adresse.CP) // 75001 <- accès explicite toujours possiblec.Ville fonctionne directement grâce à la promotion, tout en gardant c.Adresse.CP disponible. C'est l'idiome central pour réutiliser du comportement sans hiérarchie de classes.
Structs imbriquées
Section intitulée « Structs imbriquées »Un champ peut être une autre struct, nommée cette fois. À la différence de l'embedding, il n'y a pas de promotion : on accède via le nom du champ.
type Commande struct { ID string Client Client // champ nommé, pas embarqué}// accès : cmd.Client.NomComparer des structs
Section intitulée « Comparer des structs »Deux structs sont comparables avec == si tous leurs champs sont comparables (types de base, pointeurs, structs comparables). L'égalité est alors champ à champ.
Personne{"Al", 1, true} == Personne{"Al", 1, true} // trueStruct anonyme et struct vide
Section intitulée « Struct anonyme et struct vide »Une struct anonyme se déclare et s'instancie à la volée, utile pour un besoin local (un test, un payload ponctuel).
point := struct{ X, Y int }{X: 1, Y: 2}La struct vide struct{} n'occupe aucune mémoire. Son usage idiomatique est l'ensemble (set), en valeur d'une map dont seules les clés comptent.
set := map[string]struct{}{"a": {}, "b": {}}_, present := set["a"] // true, sans stocker de valeur inutileBonnes pratiques
Section intitulée « Bonnes pratiques »- Préférez l'initialisation par nom de champ : elle survit à l'ajout ou au réordonnancement de champs.
- Concevez la valeur zéro pour qu'elle soit utilisable, plutôt que d'imposer un constructeur.
- Exposez (majuscule) uniquement les champs qui doivent l'être, et documentez vos tags de sérialisation.
- Composez par embedding au lieu de simuler de l'héritage.
- Méfiez-vous de la comparabilité dès qu'une struct contient des slices ou des maps.
À retenir
Section intitulée « À retenir »- Une struct est un type personnalisé regroupant des champs typés ; Go n'a ni classes ni héritage.
- L'initialisation par nom est la forme à privilégier ; la valeur zéro est déjà exploitable.
- La casse de l'initiale décide de la visibilité et de la sérialisation ; les tags affinent le JSON.
- L'embedding promeut champs et méthodes : c'est la composition idiomatique de Go.
- Les structs sont comparables avec
==sauf si elles contiennent slices, maps ou fonctions. struct{}vide sert d'ensemble mémoire-zéro en valeur de map.