Les structs et les méthodes en Go

Apprenez à définir des structs en Go, utiliser les champs et les littéraux, imbriquer des structs et écrire des méthodes à récepteur valeur ou pointeur.

Prérequis

Objectifs pédagogiques

  • Définir des structs et construire des valeurs avec des littéraux nommés ou positionnels
  • Attacher des méthodes et choisir entre récepteur valeur et récepteur pointeur
  • Réutiliser champs et méthodes grâce aux structs imbriqués

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.

Votre progression est enregistrée uniquement dans ce navigateur. Aucun compte n’est requis.

Recherche

La recherche s’exécute entièrement dans votre navigateur.