Как сделать так, чтобы изменения слайса в функции были видны в вызывающей функции
Что проверяют
Понимает ли кандидат, что в Go всё передаётся по значению, и что слайс это заголовок из трёх полей, а не ссылка. Правильный ответ разделяет два разных случая: изменение элементов и изменение самого слайса.
Как отвечать
Передачи по ссылке в Go нет. Слайс это значение вида struct { ptr *T; len int; cap int }. При вызове функции копируется заголовок, но ptr в копии указывает на тот же массив. Отсюда следствия:
- запись
s[i] = xвидна вызывающей функции всегда, потому что пишется общий массив; s = append(s, x),s = s[:2],s = nilвидны только внутри функции: они меняют локальную копию заголовка.
Тонкость с append: если хватило cap, он пишет в тот же массив, и вызывающий, у которого len больше, эту запись увидит. Если ёмкости не хватило, аллоцируется новый массив, и связь теряется. Поведение зависит от cap, то есть с точки зрения вызывающего кода непредсказуемо. Полагаться на него нельзя.
Три рабочих способа:
- Вернуть слайс из функции. Идиоматичный вариант, ровно так устроен сам
append. - Передать
*[]T, если функция обязана менять длину на месте. - Передавать слайс как буфер и только заполнять уже существующие элементы, тогда достаточно значения (так работают
io.Reader.Readиcopy).
func fill(s []int) { s[0] = 100 } // видно снаружи
func appendBad(s []int) { s = append(s, 4) } // не видно
func appendOk(s []int) []int { return append(s, 4) }
func appendPtr(s *[]int) { *s = append(*s, 4) }
func main() {
s := []int{1, 2, 3}
fill(s)
appendBad(s)
fmt.Println(s) // [100 2 3]
s = appendOk(s)
appendPtr(&s)
fmt.Println(s) // [100 2 3 4 4]
}Если спросят про другие типы: map и chan внутри тоже копируются по значению, но само значение является дескриптором, указывающим на общую структуру, поэтому запись в мапу из функции видна снаружи. А вот m = make(map[string]int) внутри функции наружу не выйдет ровно по той же причине, что и append. Массив ([3]int) копируется целиком, изменения не видны вообще.
Где спотыкаются
Заучили формулировку «слайс это ссылочный тип, передаётся по ссылке», а потом не могут объяснить, почему append внутри функции иногда виден вызывающему, а иногда нет.