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

Ответ

В Go все аргументы, включая слайсы, передаются в функцию по значению. Однако, сам слайс — это легковесная структура (заголовок), содержащая указатель на базовый массив, длину (len) и ёмкость (cap). При передаче слайса в функцию копируется именно эта структура, а не сам массив данных.

Из-за этого возникает два сценария:

  1. Изменение элементов существующего массива: Если функция изменяет элементы слайса, не выходя за пределы его len, изменения будут видны снаружи, так как и оригинальный, и скопированный заголовок указывают на один и тот же массив.
  2. Изменение самого слайса (длины/ёмкости): Если функция расширяет слайс с помощью append, и это приводит к превышению cap, будет выделен новый, больший массив. Заголовок слайса внутри функции обновится и будет указывать на этот новый массив, но заголовок оригинального слайса останется без изменений.

Чтобы изменения длины/ёмкости были видны вне функции, есть два основных способа:

Способ 1: Возврат нового слайса (идиоматичный подход)

Это самый распространенный и рекомендуемый способ в Go. Функция возвращает измененный слайс, который нужно присвоить исходной переменной.

// appendToSlice возвращает новый слайс
func appendToSlice(s []int, vals ...int) []int {
    return append(s, vals...)
}

func main() {
    s := []int{1, 2, 3}
    // Важно переприсвоить результат, так как append может вернуть новый слайс
    s = appendToSlice(s, 4, 5)
    fmt.Println(s) // Вывод: [1 2 3 4 5]
}

Способ 2: Использование указателя на слайс

Этот способ менее популярен, но также работает. Функция принимает указатель на слайс и изменяет его по этому указателю.

// extendSlice принимает указатель на слайс и изменяет его
func extendSlice(s *[]int, vals ...int) {
    *s = append(*s, vals...)
}

func main() {
    s := []int{1, 2, 3}
    extendSlice(&s, 4, 5)
    fmt.Println(s) // Вывод: [1 2 3 4 5]
}