Aujourd’hui au programme : defer et pointeur Defer Go, à l’inverse de plusieurs langages, n’a pas de blocs try, catch, finally. C’est entre autres pourquoi la gestion des erreurs se fait souvent ainsi :
if err != nil {
// Gestion de l'erreur
}
À première vue, c’est la seule différence. Mais que se passe-t-il lorsqu’on veut s’assurer qu’une fonction est appelée ou qu’un code est exécuté après l’exécution du programme ou après une fonction ?
Un bon exemple est la fermeture d’un fichier après son ouverture. C’est là que le mot-clé defer entre en jeu :
func main() {
file, err := os.Open("fichier.txt")
if err != nil {
panic(err)
}
defer file.Close()
data := make([]byte, 2048)
for {
count, err := file.Read(data)
os.Stdout.Write(data[:count])
if err != nil {
if err.Error() != "EOF" {
log.Fatal(err)
}
break
}
}
os.Stdout.Write([]byte("\n"))
}
Ce programme ouvre le fichier fichier.txt et affiche son contenu dans la console.
Vous avez sûrement remarqué qu’en lisant le programme ligne par ligne, on ouvre le fichier et on le ferme avant même d’avoir lu son contenu.
En réalité, une fonction appelée après le mot-clé defer ne s’exécute pas immédiatement, mais seulement à la fin du bloc dans lequel elle est déclarée. Dans cet exemple, file.Close() est exécuté à la fin de la fonction main.
Dans ce programme simple, on pourrait facilement appeler file.Close() à la dernière ligne, mais dans un programme de plusieurs millions de lignes, il est facile d’oublier de fermer un fichier, ce qui peut provoquer des fuites de ressources et des erreurs d’exécution. C’est pourquoi il est préférable d’écrire la ligne defer immédiatement après l’ouverture du fichier.
Pointeur Go offre un Garbage Collector et une allocation dynamique de la mémoire, mais pour optimiser les performances et conserver une certaine simplicité, il est parfois nécessaire de manipuler des pointeurs.
Beaucoup de développeurs juniors ont peur des pointeurs, mais nous allons démystifier cela.
💡 Le passage par référence est équivalent au passage par pointeur.
func main() {
variable := 10
println(variable) // Affiche 10
// Affiche l'adresse mémoire de la variable
println(&variable)
// Crée un pointeur vers la variable avec l'opérateur & qui retourne l'adresse mémoire
pointeur := &variable
// Affiche une adresse mémoire, ex. 0xc000058710
println(pointeur)
// Change la valeur de variable via le pointeur
*pointeur = 20
// Affiche 20
println(*pointeur)
println(variable) // Affiche 20
// Crée un pointeur non initialisé
var pointeur2 *int
if pointeur2 == nil {
println("Le pointeur est nul")
}
// Déréférencer un pointeur non initialisé provoquerait une erreur de segmentation
// println(*pointeur2)
// Crée un pointeur initialisé avec la valeur par défaut de int grâce à new()
pointeur2 = new(int)
if pointeur2 != nil {
println("Le pointeur n'est plus nul")
}
}
Passage par valeur vs passage par référence Passage par valeur (copie de la variable)
type Person struct {
Name string
Age int
}
// Reçoit une copie de l'entier et ne modifie que cette copie
func ModifierInt(i int) {
i = 15
}
// Reçoit un pointeur et modifie la valeur pointée
func ModifierIntPtr(i *int) {
*i = 15
}
func main() {
entier := 10
ModifierInt(entier)
println(entier) // Affiche 10 (car une copie a été modifiée)
ModifierIntPtr(&entier)
println(entier) // Affiche 15 (car l’original a été modifié via un pointeur)
}
Passage par référence (pas de copie, modification directe)
// ModifierSlice modifie le premier élément du slice car les slices sont des références
func ModifierSlice(slice []int) {
slice[0] = 10
}
// ModifierMap modifie la valeur d'une clé car les maps sont des références
func ModifierMap(m map[string]int) {
m["one"] = 10
}
func main() {
mySlice := []int{1, 2, 3, 4, 5}
ModifierSlice(mySlice)
println(mySlice[0]) // Affiche 10
myMap := map[string]int{
"one": 1,
"two": 2,
}
ModifierMap(myMap)
println(myMap["one"]) // Affiche 10
}
Résumé 📌 Passage par valeur (une copie est créée) :
string, bool, int, float, struct Modifier ces types dans une fonction ne change pas la variable originale. 📌 Passage par référence (pas de copie, modification directe) :
map, slice Modifier ces types dans une fonction affecte directement l’original. Les pointeurs permettent de modifier des variables en place, et defer assure que certaines opérations (comme la fermeture de fichiers) soient toujours exécutées.
Commentaires