Aller au contenu
Développement medium

Structs en Go : définition, tags et composition

9 min de lecture

Go

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.

  • 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.

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.

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éordonnancements
p2 := Personne{
nom: "Bob",
age: 25,
}
// Partiel : les champs absents prennent leur valeur zéro
p3 := Personne{nom: "Charlie"}
// Pointeur vers une nouvelle instance
p4 := &Personne{nom: "Diana", age: 35}

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 // modification
fmt.Println(p2.nom) // lecture : Bob
p4.nom = "Diana Smith" // via pointeur, déréférencement implicite

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.

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).

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 direct
fmt.Println(c.Adresse.CP) // 75001 <- accès explicite toujours possible

c.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.

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.Nom

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} // true

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 inutile
  • 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.
  • 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.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn