Ответ
Это классический вопрос с подвохом. Правильный ответ: в Go всё, включая слайсы, передается в функцию по значению.
Однако, чтобы понять последствия, нужно знать, что такое слайс.
Слайс — это не сам массив данных, а легковесная структура-дескриптор, которая содержит три поля:
- Указатель на первый элемент базового массива (underlying array).
- Длина (
len
) — количество элементов в слайсе. - Емкость (
cap
) — количество элементов от начала слайса до конца базового массива.
Когда вы передаете слайс в функцию, копируется именно эта структура-дескриптор, а не сам массив данных. Обе копии дескриптора (оригинальная и в функции) указывают на один и тот же базовый массив.
Это приводит к двум важным сценариям:
1. Изменение элементов слайса
Если вы изменяете элемент слайса внутри функции, вы изменяете данные в общем базовом массиве. Эти изменения будут видны снаружи.
func modifyElements(s []int) {
s[0] = 100 // Изменение видно снаружи
}
func main() {
originalSlice := []int{1, 2, 3}
modifyElements(originalSlice)
fmt.Println(originalSlice) // Вывод: [100 2 3]
}
2. Изменение самого слайса (например, через append
)
Здесь ситуация сложнее. Функция append
может как изменить существующий массив, так и создать новый.
- Если
append
не превышает емкость (cap
): Новый элемент добавляется в тот же базовый массив. Однако изменяется только полеlen
у локальной копии слайса внутри функции. Оригинальный слайс об этом "не знает". - Если
append
превышает емкость (cap
): Go создает новый, больший массив, копирует туда старые элементы и добавляет новый. Локальная копия слайса теперь указывает на этот новый массив. Оригинальный слайс остается без изменений.
func tryToAppend(s []int) {
// Эта операция не будет видна снаружи, т.к. может быть создан новый массив,
// а s - это локальная копия дескриптора.
s = append(s, 4)
}
func main() {
originalSlice := []int{1, 2, 3}
tryToAppend(originalSlice)
fmt.Println(originalSlice) // Вывод: [1 2 3]
}
Как правильно?
Чтобы надежно изменять слайс (его длину, емкость или перенаправлять на новый массив), используйте идиоматический подход Go: возвращайте измененный слайс из функции.
func correctlyAppend(s []int) []int {
s = append(s, 4)
return s
}
func main() {
originalSlice := []int{1, 2, 3}
originalSlice = correctlyAppend(originalSlice) // Переприсваиваем результат
fmt.Println(originalSlice) // Вывод: [1 2 3 4]
}