Задача: Append и видимость элемента
Условие
Дан следующий код:
package main
import "fmt"
func appendTen(s []int) []int {
s = append(s, 10)
return s
}
func main() {
s := make([]int, 3, 6)
s[0], s[1], s[2] = 1, 2, 3
result := appendTen(s)
fmt.Println("s:", s) // что выведет?
fmt.Println("result:", result) // что выведет?
}
Вопрос
Увидим ли мы десятку в слайсе s после вызова функции appendTen?
Ответ
Нет, десятка не будет видна в исходном слайсе s. При этом она будет присутствовать в возвращаемом слайсе result.
Объяснение
Ключевой момент — поведение слайсов в Go при передаче в функцию.
-
Структура слайса: слайс — это структура из трех полей: указатель на массив, длина (len) и емкость (cap).
-
Передача по значению: в Go все передается по значению. При передаче слайса в функцию копируется структура слайса, но указатель на массив остается тем же.
-
Что происходит в
appendTen:- Исходный слайс
sимеет len=3, cap=6 - При вызове
append(s, 10)Go видит, что cap (6) > len (3), поэтому новый элемент помещается в существующий массив по индексу 3 - Создается новый слайс с len=4, cap=6, указывающий на тот же массив
- Этот новый слайс возвращается и присваивается
result
- Исходный слайс
-
Почему десятка не видна в
s:- В
mainпеременнаяsпродолжает иметь len=3 - Хотя элемент физически записан в массив по индексу 3, слайс
s"не знает" о нем, так как его длина осталась 3 - Слайс
sможет "видеть" только элементы с индексами [0, len)
- В
Демонстрация
func main() {
s := make([]int, 3, 6)
s[0], s[1], s[2] = 1, 2, 3
result := appendTen(s)
fmt.Println("s:", s) // [1 2 3]
fmt.Println("result:", result) // [1 2 3 10]
// Но если мы изменим длину s, то увидим десятку
s = s[:4]
fmt.Println("s after reslice:", s) // [1 2 3 10]
}
Edge cases и важные нюансы
-
Если cap == len: при append создается новый массив, и изменения не затронут исходный слайс:
s := make([]int, 3, 3) // cap = len result := appendTen(s) // s: [1 2 3], result: [1 2 3 10] — разные массивы -
Изменение существующих элементов: если бы мы меняли существующие элементы (индексы 0-2), изменения были бы видны в обоих слайсах, так как они разделяют один массив.
-
Возврат слайса из функции: правильный паттерн — всегда возвращать слайс из функции, если в ней мог произойти append:
func appendTen(s []int) []int { return append(s, 10) }
Вывод
Десятка не видна в исходном слайсе s из-за того, что его длина (len) не изменилась. При этом элемент физически существует в общем массиве, и если "расширить" слайс до нужной длины, он станет видимым.