Как изменить слайс внутри функции так, чтобы изменения были видны снаружи?

Ответ

В 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]
}