append в функции тихо расходится с исходным слайсом

Классический вопрос на понимание слайсов в Go — не теория ради теории, а то, что ломает код в проде, когда слайс передают в функцию «для изменения».

Слайс — это структура из указателя на массив, длины и ёмкости. Передавая слайс в функцию, вы копируете эту структуру, но указатель внутри — тот же самый массив. Пока ёмкости хватает, запись через индекс в функции видна снаружи. А вот append — отдельная история: если он не выходит за ёмкость, пишет в тот же массив и возвращает слайс с увеличенной длиной. Но длина у локальной копии, в main её не меняет.

func addOne(s []int) { s = append(s, 99) s[0] = 1 }

func main() { s := make([]int, 2, 3) s[0], s[1] = 0, 2 addOne(s) fmt.Println(s) }

На выходе [1 2], а не [1 2 99]. Запись в s[0] видна снаружи — это тот же массив. А append дописал 99 в свободную третью ячейку массива (ёмкость 3 позволяла), но длина увеличилась только у локальной копии слайса — наружу она не просочилась.

Следом спросят: а что если ёмкость была бы 2, а не 3? Тогда append внутри функции аллоцирует новый массив — и даже запись s[0] = 1 после этого изменит уже новый массив, а не исходный. Старый слайс в main останется нетронутым целиком, включая первый элемент.

Вывод, который стоит унести: слайс — это не ссылка на массив, а три поля, скопированные по значению. Делится с вызывающим кодом только тот массив, на который слайс указывал в момент вызова — и то, пока вы не вышли за его ёмкость.

Тренажёр: 1900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки