Что происходит при взятии части слайса в Go
Что проверяют
Понимаете ли вы, что слайс это не данные, а заголовок из трёх полей: указателя на массив, длины и ёмкости. От этого зависит, будете ли вы ловить баги, где правка одного слайса меняет другой, и держать в памяти мегабайты ради десятка байт.
Как отвечать
Выражение b := a[1:3] не копирует ничего. Новый заголовок смотрит в тот же массив, только со смещением:
- длина равна двум элементам, то есть разности границ;
- ёмкость считается до конца исходного массива, а не до конца выреза:
cap(b) == cap(a)-1; - запись
b[0] = 99видна вa[1], потому что это одна и та же ячейка.
Дальше главное, про append. Если в ёмкости есть запас, добавление пишет прямо в исходный массив и затирает соседний элемент оригинала. Если запаса нет, рантайм выделяет новый массив, копирует туда данные, и связь между слайсами пропадает. Результат зависит от ёмкости, отсюда плавающие баги:
a := []int{1, 2, 3, 4}
b := a[1:3]
b = append(b, 99) // a стало [1 2 3 99]Как отрезать связь: скопировать нужное через copy в свежий слайс либо взять трёхиндексный вырез a[1:3:3], который прижимает ёмкость к границе и заставляет append выделить новый массив.
Где спотыкаются
Помнят про общую память, но забывают про ёмкость. Пока правят элементы, всё сходится, ломается на добавлении: один и тот же код то затирает чужие данные, то нет, в зависимости от того, сколько места осталось в массиве.
Не думают про удержание памяти. Вырез в десять байт из массива на несколько мегабайт держит живым весь массив: сборщик видит ссылку на общий массив, а не на кусок. Лечится явным копированием нужной части.
Переносят правило на строки как есть. Подстрока тоже не копирует данные, но строка неизменяема, поэтому там опасно только удержание памяти, а чужих правок через подстроку не бывает.