Pièges des tranches, canaux et nil en Go : analyse approfondie des détails d'implémentation
Les tranches (slices) en Go semblent simples, mais un comportement prévisible exige une compréhension de leurs mécanismes internes. Puisqu'une tranche fait référence à un tableau sous-jacent, des modifications inattendues et des fuites de mémoire peuvent survenir.
Examinons le cas de base de la réutilisation du tableau :
a := []int{1, 2, 3, 4}
b := a[1:3] // b = [2, 3]
b[0] = 99
fmt.Println(a)
Sortie : [1 99 3 4]. Modifier b affecte le tableau original a.
Ajoutons maintenant append à a :
a := []int{1, 2, 3, 4}
b := a[1:3]
a = append(a, 5)
b[0] = 99
fmt.Println(a)
Sortie : [1 2 3 4 5]. L'opération append a alloué un nouveau tableau, donc les modifications apportées à b n'affectent plus l'original.
Subtilités de append et de la capacité
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3], 5)
fmt.Println(a)
}
Sortie : [1 2 3 5]. Il y avait assez de capacité, donc aucune réallocation n’a eu lieu.
Avec une limitation explicite de capacité :
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3:3], 5)
fmt.Println(a)
}
Sortie : [1 2 3 4]. La limitation de capacité a déclenché une réallocation.
Étendre une tranche au-delà de sa longueur :
func main() {
s := []int{1, 2, 3, 4, 5}[1:3]
fmt.Println(s)
extendedSlice := s[:4]
fmt.Println(extendedSlice)
}
Sortie :
[2 3]
[2 3 4 5]
Fuites de mémoire et passage de tranches
Une petite tranche provenant d’un grand tableau conserve tout le tampon :
bigArray := make([]int, 1e6)
smallSlice := bigArray[:10]
Le passage par valeur modifie le tableau sous-jacent sans changer la capacité :
func modifySlice(s []int) {
s[0] = 99
s = append(s, 100)
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s)
}
Sortie : [99 2 3].
Boucle avec références à un élément mutable :
func main() {
s := []int{}
refs := []*int{}
for i := 0; i < 5; i++ {
s = append(s, i)
refs = append(refs, &s[0])
}
*refs[4] = 4
*refs[0] = 99999
fmt.Println(s)
}
Sortie : [4 1 2 3 4].
Incohérence de nil pour les tranches et les maps
var s []int
fmt.Println(len(s)) // 0
s = append(s, 1)
var m map[string]string
fmt.Println(len(m)) // 0
m["key"] = "value" // panique
Une tranche nil fonctionne bien, mais une map nil provoque une panique lors de l’assignation.
Chaînes comme octets
Les chaînes stockent des octets :
func main() {
str := "å"
fmt.Println(str[1]) // 165
}
func main() {
str := "Three"
fmt.Println(len(str)) // 6
}
Masquage des identifiants prédéfinis
func main() {
true := false
uint := "bob"
string := 0
fmt.Printf("%v, %v, %v", true, uint, string)
}
Sortie : false, bob, 0.
Canaux : lecture et fermeture
La lecture nécessite de vérifier l’état du canal :
ch := getCountChannel[int]()
if v, ok := <-ch; ok {
fmt.Println(v)
} else {
fmt.Println("channel closed")
}
Affecter nil à un canal désactive la branche select :
var in <-chan int = ch
if paused {
in = nil
}
select {
case v := <-in:
fmt.Println("got", v)
case <-ctx.Done():
return
}
Après close(), les valeurs zéro sont lues depuis le canal :
func main() {
ch := make(chan int, 1)
ch <- 0
close(ch)
fmt.Println(<-ch) // 0 true
fmt.Println(<-ch) // 0 false
fmt.Println(<-ch) // 0 false
}
Avec vérification :
v, ok := <-ch
fmt.Println(v, ok)
nil typé dans les interfaces
type MyErr struct{}
func (MyErr) Error() string { return "boom" }
func f() error {
var e *MyErr = nil
return e
}
func main() {
err := f()
fmt.Println(err == nil) // false
}
Une interface contient à la fois le type et la valeur. Solution :
func f() error {
var e *MyErr = nil
if e == nil {
return nil
}
return e
}
Problèmes avec for range et les pointeurs
vals := []int{1, 2, 3}
ptrs := []*int{}
for _, v := range vals {
ptrs = append(ptrs, &v)
}
fmt.Println(*ptrs[0], *ptrs[1], *ptrs[2]) // 3 3 3
La variable v est réutilisée à chaque itération.
Points clés :
- Les tranches partagent un tableau sous-jacent : les modifications sont visibles partout.
appendpeut réallouer de la mémoire lorsque la capacité est dépassée.- Les tranches
nilpermettentappend; les mapsnilnon. - Les chaînes stockent des octets ;
len()compte les octets. - Un
niltypé dans une interface n’est pas égal ànil. for rangecrée une seule variable de boucle.
— Editorial Team
Aucun commentaire pour le moment.