
Structs (définition, création, accès aux champs)
Apprenez à définir vos propres types de données composites en Go avec les structs. Maîtrisez la création d'instances et l'accès aux champs de vos structures.
Modéliser le monde réel : Introduction aux Structs
Jusqu'à présent, nous avons manipulé des types de données de base (int, string, bool) et des collections comme les slices et les maps. Cependant, pour représenter des entités plus complexes du monde réel ou de notre domaine d'application (comme un utilisateur, un produit, une configuration géométrique), nous avons besoin de regrouper plusieurs informations, potentiellement de types différents, en une seule unité logique. C'est précisément le rôle des structs (structures) en Go.
Une struct est un type de données composite qui permet de définir un ensemble de champs nommés. Chaque champ a un nom et un type spécifique. Une définition de struct agit comme un plan ou un modèle pour créer des variables (appelées instances) qui contiendront des données organisées selon cette structure.
Les structs sont fondamentales en Go pour créer des types personnalisés, organiser les données de manière significative et construire des abstractions claires. Elles sont au coeur de la modélisation de données dans les applications Go.
Définir une Struct : La syntaxe `type ... struct`
La définition d'un nouveau type structuré se fait à l'aide du mot-clé `type`, suivi du nom que vous souhaitez donner à votre nouveau type, puis du mot-clé `struct` et enfin d'une liste de champs entre accolades `{}`.
Syntaxe :
type NomDuType struct {
NomChamp1 TypeChamp1
NomChamp2 TypeChamp2
// ... autres champs
}Chaque ligne à l'intérieur des accolades définit un champ : son nom et son type. Les noms de champs suivent les mêmes règles de visibilité que les autres identifiants en Go : s'ils commencent par une majuscule, ils sont exportés (accessibles depuis d'autres paquets), sinon ils sont non exportés (privés au paquet courant).
Exemple : définir une struct pour représenter une personne.
package main
import "fmt"
// Définition du type 'Personne'
type Personne struct {
Nom string // Champ exporté
Prenom string // Champ exporté
age int // Champ non exporté (commence par minuscule)
EstActif bool // Champ exporté
}
func main() {
// (Création et utilisation vues dans les sections suivantes)
fmt.Println("Définition de la struct Personne effectuée.")
}Ici, nous avons défini un nouveau type `Personne` avec quatre champs : `Nom`, `Prenom`, `age` et `EstActif`. Notez que `age` n'est accessible qu'à l'intérieur du paquet `main` car il commence par une minuscule.On peut également définir des champs anonymes (embedding), mais cela sort du cadre de cette introduction de base.
Créer des instances de Struct : Variables et Littéraux
Une fois qu'un type struct est défini, vous pouvez créer des variables (instances) de ce type. Il y a plusieurs façons de le faire :
1. Déclaration `var` (valeur zéro) : Déclarer une variable de type struct sans initialisation explicite lui assigne la valeur zéro de la struct. La valeur zéro d'une struct est une instance où tous ses champs ont leur propre valeur zéro (0 pour les nombres, "" pour les strings, false pour les bool, nil pour les pointeurs/slices/maps/etc.).
var p1 Personne
fmt.Printf("p1 (valeur zéro): %+v\n", p1) // Affiche: p1 (valeur zéro): {Nom: Prenom: age:0 EstActif:false}
// %+v dans fmt affiche aussi les noms des champs2. Littéral de struct avec noms de champs : C'est la manière la plus courante et la plus lisible d'initialiser une struct. Vous spécifiez explicitement le nom de chaque champ suivi de sa valeur. L'ordre n'a pas d'importance, et vous pouvez omettre des champs (ils prendront leur valeur zéro).
p2 := Personne{
Nom: "Dupont",
Prenom: "Alice",
age: 30,
EstActif: true,
}
fmt.Printf("p2 (littéral avec noms): %+v\n", p2)
// Affiche: p2 (littéral avec noms): {Nom:Dupont Prenom:Alice age:30 EstActif:true}
p3 := Personne{Nom: "Martin", EstActif: true} // Prenom et age auront leur valeur zéro
fmt.Printf("p3 (partiel): %+v\n", p3)
// Affiche: p3 (partiel): {Nom:Martin Prenom: age:0 EstActif:true}3. Littéral de struct sans noms de champs : Vous pouvez omettre les noms des champs, mais vous devez alors fournir une valeur pour chaque champ dans l'ordre exact où ils sont définis dans la struct. Cette forme est moins flexible et moins lisible, et donc moins recommandée, car elle casse si l'ordre des champs change.
// Moins recommandé !
p4 := Personne{"Lefevre", "Bob", 25, false}
fmt.Printf("p4 (littéral sans noms): %+v\n", p4)
// Affiche: p4 (littéral sans noms): {Nom:Lefevre Prenom:Bob age:25 EstActif:false}4. Avec `new()` (pointeur vers valeur zéro) : La fonction intégrée `new(T)` alloue de la mémoire pour une valeur de type `T`, initialise cette mémoire à la valeur zéro de `T`, et retourne un pointeur (`*T`) vers cette mémoire.
p5Ptr := new(Personne)
fmt.Printf("p5Ptr (new): type=%T, valeur pointée=%+v\n", p5Ptr, *p5Ptr)
// Affiche: p5Ptr (new): type=*main.Personne, valeur pointée={Nom: Prenom: age:0 EstActif:false}
// p5Ptr est un pointeur (*Personne), *p5Ptr est la valeur Personne5. Pointeur vers un littéral : Vous pouvez aussi obtenir un pointeur vers une struct en prenant l'adresse (`&`) d'un littéral de struct. C'est très courant.
p6Ptr := &Personne{Nom: "Durand", Prenom: "Claire", age: 40, EstActif: true}
fmt.Printf("p6Ptr (&littéral): type=%T, valeur pointée=%+v\n", p6Ptr, *p6Ptr)
// Affiche: p6Ptr (&littéral): type=*main.Personne, valeur pointée={Nom:Durand Prenom:Claire age:40 EstActif:true}Accéder aux champs : L'opérateur point (`.`)
Une fois que vous avez une instance d'une struct (que ce soit la valeur elle-même ou un pointeur vers elle), vous pouvez accéder à ses champs (lire ou modifier leur valeur) en utilisant l'opérateur point (`.`) suivi du nom du champ.
Syntaxe : `instance.NomChamp`
Exemple de lecture et d'écriture sur une valeur struct :
var p7 Personne
p7.Nom = "Simon"
p7.Prenom = "Paul"
p7.age = 50 // OK car dans le même paquet
p7.EstActif = true
fmt.Printf("p7: Nom=%s, Age=%d, Actif=%t\n", p7.Nom, p7.age, p7.EstActif)
// Affiche: p7: Nom=Simon, Age=50, Actif=trueUne caractéristique très pratique de Go est que l'accès aux champs via un pointeur de struct utilise exactement la même notation avec le point (`.`). Go déréférence automatiquement le pointeur pour vous. Vous n'avez pas besoin d'utiliser la syntaxe `(*pointeur).NomChamp` (bien qu'elle fonctionne aussi).
Exemple avec un pointeur :
p8Ptr := new(Personne)
// Accès via pointeur - Go déréférence automatiquement
p8Ptr.Nom = "Moreau"
p8Ptr.age = 35
fmt.Printf("p8Ptr: Nom=%s, Age=%d\n", p8Ptr.Nom, p8Ptr.age)
// Affiche: p8Ptr: Nom=Moreau, Age=35
// La syntaxe explicite fonctionne aussi, mais est moins idiomatique :
// (*p8Ptr).Prenom = "Lucas"
// fmt.Println((*p8Ptr).Prenom)
Cette simplification syntaxique rend le travail avec des pointeurs de structs beaucoup plus agréable en Go.Les Structs : Blocs de construction essentiels
Les structs sont un outil fondamental pour la modélisation de données en Go. Elles vous permettent de définir vos propres types composites en regroupant des champs liés, améliorant ainsi l'organisation, la lisibilité et la maintenabilité de votre code.
Vous savez maintenant comment :
- Définir un type struct avec `type Nom struct { ... }`.
- Créer des instances de structs via la valeur zéro (`var`), des littéraux (avec ou sans noms de champs), `new()` ou `&Nom{...}` pour obtenir des pointeurs.
- Accéder aux champs d'une instance (valeur ou pointeur) en utilisant l'opérateur point `.`.
Les structs sont souvent utilisées en combinaison avec les méthodes (fonctions associées à un type) et les interfaces pour implémenter des comportements et des abstractions plus complexes, sujets que nous aborderons prochainement.