← Назад к вопросам
Что копируется при передаче слайса в функцию?
2.2 Middle🔥 182 комментариев
#Основы Go
Комментарии (2)
🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Передача слайса в функцию в Go
При передаче слайса в функцию в Go копируется заголовок слайса (slice header), но не копируются сами элементы массива, на которые слайс ссылается. Это критически важное различие, которое определяет поведение слайсов в Go.
Структура заголовка слайса
Заголовок слайса — это структура, содержащая три поля:
- Указатель на базовый массив
- Длина (length) — количество элементов, доступных в слайсе
- Ёмкость (capacity) — общий размер базового массива от начального элемента слайса
// Пример структуры заголовка слайса (не фактический код из runtime)
type sliceHeader struct {
Pointer unsafe.Pointer // указатель на базовый массив
Length int // текущая длина
Capacity int // максимальная ёмкость
}
Что происходит при передаче
func main() {
original := []int{1, 2, 3, 4, 5}
fmt.Printf("До: %v\n", original) // [1 2 3 4 5]
modifySlice(original)
fmt.Printf("После: %v\n", original) // [100 2 3 4 5]
}
func modifySlice(s []int) {
s[0] = 100 // Изменение отразится на оригинале
}
В этом примере:
- При вызове
modifySlice(original)копируется заголовок слайса - Копия заголовка содержит тот же указатель на базовый массив
- Изменения элементов через индекс (
s[0] = 100) меняют оригинальный массив - Поэтому изменения видны в вызывающей функции
Важные нюансы поведения
Изменение длины и ёмкости
func appendToSlice(s []int) {
s = append(s, 100)
// Это НЕ изменит оригинальный слайс в main()
// потому что изменяется копия заголовка
}
func main() {
s := []int{1, 2, 3}
appendToSlice(s)
fmt.Println(s) // [1 2 3], а не [1 2 3 100]
}
Решение через возврат значения
func appendAndReturn(s []int) []int {
return append(s, 100)
}
func main() {
s := []int{1, 2, 3}
s = appendAndReturn(s) // Теперь s = [1 2 3 100]
}
Передача по указателю
Если нужно изменить сам слайс (его длину/ёмкость) внутри функции:
func modifyWithPointer(s *[]int) {
*s = append(*s, 100)
(*s)[0] = 999
}
func main() {
s := []int{1, 2, 3}
modifyWithPointer(&s)
fmt.Println(s) // [999 2 3 100]
}
Сравнение с массивами
Для контраста, массивы копируются полностью:
func modifyArray(arr [3]int) {
arr[0] = 100 // Изменяется только копия
}
func main() {
arr := [3]int{1, 2, 3}
modifyArray(arr)
fmt.Println(arr) // [1 2 3] - без изменений
}
Практические рекомендации
- Для чтения элементов передавайте слайс как есть — изменения элементов будут видны снаружи
- Для изменения длины/ёмкости либо возвращайте новый слайс, либо передавайте указатель на слайс
- Помните об аллокациях —
appendможет создать новый массив при превышении capacity - Используйте
copy()для создания настоящих независимых копий:
func createCopy(s []int) []int {
newSlice := make([]int, len(s))
copy(newSlice, s)
return newSlice
}
Вывод
Передача слайса в функцию — это копирование по значению заголовка слайса, а не копирование данных. Это делает слайсы эффективными для передачи (копируется всего 24 байта на 64-битной архитектуре), но требует понимания, что:
- Изменение элементов отражается на оригинале
- Изменение длины или ёмкости (через append) не отражается на оригинале, если не используется возврат значения или указатель
- Это "полу-ссылочный" тип данных, сочетающий эффективность передачи с необходимостью явного управления расширением