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

Ответ

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

Ответ 18+ 🔞

А, ну вот, опять про эти ваши слайсы в Go, блядь! Сидишь, думаешь, что всё понял, а потом — хоп! — и нихуя не работает, как ожидал. Давайте разберёмся, как с этим чёртовым дескриптором не облажаться.

Смотри, сука, в чём прикол. Когда ты передаёшь слайс в функцию, копируется не сам массив с данными, а этакая заглушка, дескриптор, блядь. В нём лежит указатель на массив, длина и вместимость. И вот из-за этой хуйни есть три основных подхода, чтобы твои изменения не сгинули в никуда.

Способ 1: Вернуть новый слайс (Нормальный, человеческий способ)

Самый частый и безопасный, ёпта. Функция жрёт слайс, делает с ним что надо и выплёвывает обратно новый. Ты его просто присваиваешь своей старой переменной — и всё, пиздецки просто.

// appendToSlice возвращает новый слайс
func appendToSlice(s []int) []int {
    // append может нахуярить новый массив, если места не хватит
    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]
}

Вот и вся магия, ебать её в сраку. Главное — понимать, когда ты ковыряешь данные в массиве, а когда пытаешься растянуть или укоротить сам слайс. А то получится как у того Герасима — хотел как лучше, а получилось «Муму» в озеро, пиздец.