Ответ
В Go все передается в функцию по значению. Когда вы передаете слайс, копируется его дескриптор (заголовок), а не лежащий в его основе массив данных. Дескриптор содержит указатель на массив, длину (len
) и вместимость (cap
).
Из-за этой особенности есть несколько способов модификации слайса с видимостью для вызывающей функции.
Способ 1: Вернуть новый слайс (Идиоматичный подход)
Это самый распространенный и безопасный способ. Функция принимает слайс и возвращает его измененную версию. Вызывающий код должен присвоить результат переменной.
// appendToSlice возвращает новый слайс
func appendToSlice(s []int) []int {
// Встроенная функция append может вернуть новый массив, если не хватает capacity
return append(s, 42)
}
func main() {
s := []int{1, 2, 3}
s = appendToSlice(s) // Перезаписываем старый слайс новым
fmt.Println(s) // Вывод: [1 2 3 42]
}
Способ 2: Передать указатель на слайс
Этот способ позволяет изменять сам дескриптор слайса (например, его длину или указатель на массив), что делает изменения видимыми снаружи. Используется реже, в основном в функциях, которым нужно модифицировать несколько слайсов, или для оптимизации производительности в специфичных случаях.
// modifySlice принимает указатель и изменяет слайс по месту
func modifySlice(s *[]int) {
*s = append(*s, 42)
}
func main() {
s := []int{1, 2, 3}
modifySlice(&s) // Передаем адрес переменной слайса
fmt.Println(s) // Вывод: [1 2 3 42]
}
Важный нюанс: изменение элементов существующего слайса
Если вы не меняете длину или вместимость слайса, а только изменяете значения его существующих элементов, то изменения будут видны и без возврата значения или указателя. Это происходит потому, что скопированный дескриптор все еще указывает на тот же самый базовый массив.
func changeFirstElement(s []int) {
if len(s) > 0 {
s[0] = 99
}
}
func main() {
s := []int{1, 2, 3}
changeFirstElement(s)
fmt.Println(s) // Вывод: [99 2 3]
}