Un struct regroupe des valeurs liées dans un seul type nommé. C’est ainsi que l’on modélise une « chose » en Go — un utilisateur, un rectangle, une requête HTTP. Les méthodes attachent ensuite un comportement à ce type.
Définir un struct
Vous déclarez un struct avec type ... struct et listez ses champs avec un nom
et un type. Les champs (et les types) exportés commencent par une majuscule.
type User struct {
Name string
Age int
}
Créer des valeurs de struct
Vous construisez une valeur avec un littéral de struct. Préférez les champs nommés : le code reste lisible et continue de fonctionner si l’ordre des champs change. Un struct déclaré sans littéral reçoit la valeur zéro de chaque champ.
u1 := User{Name: "Ada", Age: 36} // champs nommés (préférable)
u2 := User{"Alan", 41} // positionnel : l’ordre compte
var u3 User // valeurs zéro : "" et 0
u1.Age = 37 // lire et modifier un champ avec le point
Les méthodes
Une méthode est une fonction dotée d’un récepteur écrit avant son nom. Le
récepteur lie la fonction au type : vous l’appelez avec valeur.Méthode().
func (u User) Greeting() string {
return "Salut, je suis " + u.Name
}
Récepteur valeur ou récepteur pointeur
Un récepteur valeur (u User) travaille sur une copie : il ne peut pas
modifier l’original. Un récepteur pointeur (u *User) travaille sur la
valeur réelle : il peut modifier les champs. Utilisez un récepteur pointeur
lorsque la méthode doit modifier le struct, ou lorsque le struct est volumineux
et que le copier serait coûteux.
// Lecture seule : un récepteur valeur convient.
func (u User) Greeting() string {
return "Salut, je suis " + u.Name
}
// Modifie le struct : il faut un récepteur pointeur.
func (u *User) Birthday() {
u.Age++
}
Les structs imbriqués
Imbriquer un struct dans un autre (en écrivant le type sans nom de champ) promeut ses champs et ses méthodes vers le type englobant. C’est la version légère de la composition en Go — préférez-la aux hiérarchies d’héritage profondes.
type Animal struct {
Name string
}
type Dog struct {
Animal // imbriqué
Breed string
}
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Name) // promu depuis Animal : "Rex"
Un exemple complet
Le programme ci-dessous définit un Rectangle avec une méthode Area en
lecture seule et une méthode Scale qui modifie l’état. Il compile et s’exécute
tel quel.
package main
import "fmt"
// Rectangle regroupe une largeur et une hauteur sous un même type.
type Rectangle struct {
Width float64
Height float64
}
// Area renvoie l'aire du rectangle. Un récepteur valeur suffit,
// car la méthode ne fait que lire les champs.
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Scale multiplie les deux côtés sur place. Elle a besoin d'un récepteur
// pointeur pour que le changement soit visible par l'appelant.
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
r := Rectangle{Width: 3, Height: 4}
fmt.Printf("aire : %.1f\n", r.Area())
r.Scale(2)
fmt.Printf("aire mise à l'échelle : %.1f\n", r.Area())
}
Son exécution affiche :
aire : 12.0
aire mise à l'échelle : 48.0
Erreurs fréquentes
- Attendre qu’un récepteur valeur modifie l’original. Il travaille sur une copie ; le struct de l’appelant reste intact. Passez à un récepteur pointeur.
- Mélanger les types de récepteur sur un même type. Choisissez le récepteur pointeur pour un type qui doit être modifié, et utilisez-le partout pour ce type.
- Comparer des structs contenant des slices ou des maps. Un struct est
comparable avec
==seulement si tous ses champs le sont ; un struct contenant une slice ou une map ne l’est pas. - Se fier aux littéraux positionnels. Ils cassent en silence quand on ajoute ou réordonne des champs. Nommez les champs.
À vous de jouer
Définissez un struct Counter avec un champ count et une méthode Increment
à récepteur pointeur, puis incrémentez-le trois fois et affichez le total.
Afficher la correction
package main
import "fmt"
// Counter contient un total courant.
type Counter struct {
count int
}
// Increment ajoute un. Le récepteur pointeur lui permet de modifier la valeur.
func (c *Counter) Increment() {
c.count++
}
func main() {
c := Counter{}
c.Increment()
c.Increment()
c.Increment()
fmt.Println(c.count)
}Comme Increment utilise un récepteur pointeur, chaque appel met à jour le même
Counter : le programme affiche 3. Avec un récepteur valeur, il afficherait
0.
Résumé
- Un struct regroupe des champs nommés en un type ; construisez les valeurs avec des littéraux nommés.
- Les méthodes attachent un comportement via un récepteur écrit avant le nom.
- Le récepteur valeur travaille sur une copie ; le récepteur pointeur peut modifier le struct.
- L’imbrication promeut champs et méthodes : c’est la composition à la façon de Go.
Ensuite, apprenez comment les types partagent un comportement grâce aux interfaces en Go. Si les maps sont encore fraîches, voyez comment elles s’associent aux structs dans les maps en Go.