Světelné pasti v Go: slajsy, kanály a nil
Slajsy v Go vypadají jednoduše, ale pro předvídatelné chování je třeba pochopit jejich interní implementaci. Slajsy odkazují na základní pole, což může vést k neočekávaným změnám a únikům paměti.
Zvažme základní případ opakovaného použití pole:
a := []int{1, 2, 3, 4}
b := a[1:3] // b = [2, 3]
b[0] = 99
fmt.Println(a)
Výstup: [1 99 3 4]. Změna v b ovlivňuje původní pole a.
Přidejme append k a:
a := []int{1, 2, 3, 4}
b := a[1:3]
a = append(a, 5)
b[0] = 99
fmt.Println(a)
Výstup: [1 2 3 4 5]. append vyhradil nové pole, takže změna v b nemá žádný dopad na původní pole.
Jemnosti funkce append a kapacity
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3], 5)
fmt.Println(a)
}
Výstup: [1 2 3 5]. Kapacita byla dostatečná, pole nebylo přerozděleno.
S explicitním nastavením kapacity:
func main() {
a := []int{1, 2, 3, 4}
_ = append(a[:3:3], 5)
fmt.Println(a)
}
Výstup: [1 2 3 4]. Omezení kapacity způsobilo přerozdělení pole.
Rozšíření slajsu mimo délku:
func main() {
s := []int{1, 2, 3, 4, 5}[1:3]
fmt.Println(s)
extendedSlice := s[:4]
fmt.Println(extendedSlice)
}
Výstup:
[2 3]
[2 3 4 5]
Úniky paměti a předávání slajsových proměnných
Malý slajsový úsek z velkého pole udržuje celý buffer:
bigArray := make([]int, 1e6)
smallSlice := bigArray[:10]
Předávání hodnoty mění podkladové pole, ale nepřesahuje kapacitu:
func modifySlice(s []int) {
s[0] = 99
s = append(s, 100)
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s)
}
Výstup: [99 2 3].
Cyklus s odkazy na měnitelný prvek:
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)
}
Výstup: [4 1 2 3 4].
Nesrovnalost nil pro slajsy a mapy
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
Slajsová proměnná s hodnotou nil funguje, mapa nikoli.
Řetězce jako bajty
Řetězce ukládají bajty:
func main() {
str := "å"
fmt.Println(str[1]) // 165
}
func main() {
str := "Three"
fmt.Println(len(str)) // 6
}
Příkrytí předdefinovaných identifikátorů
func main() {
true := false
uint := "bob"
string := 0
fmt.Printf("%v, %v, %v", true, uint, string)
}
Výstup: false, bob, 0.
Kanály: čtení a uzavírání
Čtení vyžaduje ověření stavu:
ch := getCountChannel[int]()
if v, ok := <-ch; ok {
fmt.Println(v)
} else {
fmt.Println("kanál uzavřen")
}
Nulování kanálu deaktivuje větev select:
var in <-chan int = ch
if paused {
in = nil
}
select {
case v := <-in:
fmt.Println("dostali jsme", v)
case <-ctx.Done():
return
}
Po zavření kanálu se čtou nulové hodnoty:
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
}
S kontrolou:
v, ok := <-ch
fmt.Println(v, ok)
Typizovaný nil v rozhraních
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
}
Rozhraní obsahuje typ i hodnotu. Řešení:
func f() error {
var e *MyErr = nil
if e == nil {
return nil
}
return e
}
Problémy s for range a ukazateli
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
Proměnná v je režírována.
Důležité:
- Slajsy odkazují na společné pole: změny jsou viditelné všude.
appendmůže přerozdělit paměť při překročení kapacity.nilslajsová proměnná umožňujeappend,nilmapa ne.- Řetězce ukládají bajty,
len()počítá bajty. - Rozhraní s typizovaným
nilnení rovnonil. for rangevytváří jednu proměnnou cyklu.
— Editorial Team
Zatím žádné komentáře.