Перейти к содержанию

Слайсы

TL;DR

  • Slice = struct{ ptr, len, cap }. Под капотом — массив.
  • append может вернуть слайс с новым backing array, если cap не хватает.
  • Опасность: shared backing array.
  • Правильно копировать: copy(dst, src).

Анатомия

slice header:
+--------+--------+--------+
|  ptr   |  len   |  cap   |
+--------+--------+--------+
   |
   v
backing array: [ a | b | c | d | e | _ | _ ]
                            ^
                          cap-граница

Создание:

s := []int{1, 2, 3}                  // len=3, cap=3
s := make([]int, 3)                  // len=3, cap=3
s := make([]int, 3, 10)              // len=3, cap=10

append

s := make([]int, 0, 4)
s = append(s, 1, 2, 3, 4)   // влезло в cap=4, без аллокации
s = append(s, 5)             // переаллокация: cap → ~8

Когда cap исчерпан, runtime аллоцирует новый массив (обычно 2*cap для маленьких, x1.25 для больших), копирует данные, возвращает новый slice.

Shared backing — главная гочча

a := []int{1, 2, 3, 4, 5}
b := a[:3]
b = append(b, 99)
fmt.Println(a)  // [1 2 3 99 5]  — a[3] перезаписан!
fmt.Println(b)  // [1 2 3 99]

b[:3] имеет cap=5 (унаследован от a). append пишет в a[3] потому что есть место в cap. Если бы не было — был бы новый backing.

Правильно — отрезать cap руками:

b := a[:3:3]   // полная форма: low:high:max → cap = max-low = 3
b = append(b, 99)
fmt.Println(a)  // [1 2 3 4 5]   — без сюрпризов

copy

src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src)   // вернёт min(len(dst), len(src))

Никогда не используй dst := src для глубокой копии — это копирует только header, backing остаётся общим.

Передача в функцию

func mutate(s []int) { s[0] = 99 }     // ✅ изменяет
func grow(s []int)   { s = append(s, 9) }  // ❌ изменения header'а теряются

Чтобы grow работал — возвращай новый slice:

func grow(s []int) []int {
    return append(s, 9)
}
s = grow(s)

📖 Тренируйся на задачах: must-solve-100 → C-001..C-004.