Fallstricke von Slices, Channels und nil in Go: Tiefgang in Implementierungsdetails
Go-Slices wirken einfach, doch ein vorhersehbares Verhalten erfordert das Verständnis ihrer internen Mechanismen. Da Slices auf ein zugrundeliegendes Array verweisen, können unerwartete Änderungen und Speicherlecks auftreten.
Betrachten wir den grundlegenden Fall der Array-Wiederverwendung:
a := []int{1, 2, 3, 4}
b := a[1:3] // b = [2, 3]
b[0] = 99
fmt.Println(a)
Ausgabe: [1 99 3 4]. Die Änderung an b wirkt sich direkt auf das ursprüngliche Array a aus.
Nun fügen wir mit append an a hinzu:
a := []int{1, 2, 3, 4}
b := a[1:3]
a = append(a, 5)
b[0] = 99
fmt.Println(a)
Ausgabe: [1 2 3 4 5]. Die append-Operation hat ein neues Array allokiert, sodass Änderungen an b nicht mehr das Original beeinflussen.
Feinheiten von append und Kapazität
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3], 5)
fmt.Println(a)
}
Ausgabe: [1 2 3 5]. Es gab genügend Kapazität, daher erfolgte keine Neuallokation.
Mit expliziter Beschränkung der Kapazität:
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3:3], 5)
fmt.Println(a)
}
Ausgabe: [1 2 3 4]. Die Begrenzung der Kapazität löste eine Neuallokation aus.
Erweitern eines Slices über seine Länge hinaus:
func main() {
s := []int{1, 2, 3, 4, 5}[1:3]
fmt.Println(s)
extendedSlice := s[:4]
fmt.Println(extendedSlice)
}
Ausgabe:
[2 3]
[2 3 4 5]
Speicherlecks und Übergeben von Slices
Ein kleiner Slice aus einem großen Array hält den gesamten Puffer:
bigArray := make([]int, 1e6)
smallSlice := bigArray[:10]
Übergeben per Wert ändert das zugrundeliegende Array, verändert aber die Kapazität nicht:
func modifySlice(s []int) {
s[0] = 99
s = append(s, 100)
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s)
}
Ausgabe: [99 2 3].
Schleife mit Referenzen auf ein veränderliches Element:
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)
}
Ausgabe: [4 1 2 3 4].
Inkonsistenz von nil für Slices und 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" // panic
Ein nil-Slice funktioniert problemlos, aber ein nil-Map panics bei Zuweisung.
Strings als Bytes
Strings speichern Bytes:
func main() {
str := "å"
fmt.Println(str[1]) // 165
}
func main() {
str := "Three"
fmt.Println(len(str)) // 6
}
Überschreiben vordefinierter Bezeichner
func main() {
true := false
uint := "bob"
string := 0
fmt.Printf("%v, %v, %v", true, uint, string)
}
Ausgabe: false, bob, 0.
Channels: Lesen und Schließen
Lesen erfordert die Prüfung des Kanalzustands:
ch := getCountChannel[int]()
if v, ok := <-ch; ok {
fmt.Println(v)
} else {
fmt.Println("channel closed")
}
Ein Kanal auf nil setzen deaktiviert den select-Zweig:
var in <-chan int = ch
if paused {
in = nil
}
select {
case v := <-in:
fmt.Println("got", v)
case <-ctx.Done():
return
}
Nach close() werden Nullwerte vom Kanal gelesen:
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
}
Mit Prüfung:
v, ok := <-ch
fmt.Println(v, ok)
Typisiertes nil in Schnittstellen
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
}
Eine Schnittstelle speichert sowohl Typ als auch Wert. Lösung:
func f() error {
var e *MyErr = nil
if e == nil {
return nil
}
return e
}
Probleme mit for-range und Zeigern
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
Die Variable v wird in jeder Iteration wiederverwendet.
Wichtige Erkenntnisse:
- Slices teilen sich ein zugrundeliegendes Array: Änderungen sind überall sichtbar.
appendkann Speicher neu allozieren, wenn die Kapazität überschritten wird.nil-Slices erlaubenappend;nil-Maps nicht.- Strings speichern Bytes;
len()zählt Bytes. - Typisiertes
nilin Schnittstellen ist nicht gleichnil. for rangeerstellt eine einzige Schleifenvariable.
— Editorial Team
Noch keine Kommentare.