Что выведет код? Slice и append
Условие
Определите, что выведет следующий код:
package main
import "fmt"
func main() {
s := make([]int, 0, 2)
doSomething(s)
fmt.Println(s)
}
func doSomething(a []int) {
a = append(a, 1)
}
Вопросы
- Что выведет программа?
- Почему?
- Как изменить код, чтобы изменения были видны в main?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Это классическая задача, которая показывает критическое различие между слайсами в Go. Ответ требует глубокого понимания того, как слайсы передаются в функции и как работает append.
Ответ на вопросы
1. Что выведет программа?
[]
Отправка: пустой слайс.
2. Почему?
Проблема заключается в том, как Go передаёт слайсы в функции:
- Слайсы передаются по значению, но это не означает копирование содержимого
- Каждый слайс — это структура с тремя полями: ptr (указатель на данные), len (длина), cap (capacity)
- Когда вы передаёте слайс в функцию, копируется сама структура слайса, не данные
Что происходит в коде:
1. main: s = make([]int, 0, 2)
→ создаётся слайс {ptr→[_,_], len=0, cap=2}
2. main: doSomething(s)
→ копируется структура слайса в параметр a
→ a = {ptr→[_,_], len=0, cap=2} (копия, но указатель на те же данные)
3. doSomething: a = append(a, 1)
→ append НЕ переуправляет переменную a в main
→ append возвращает **новый слайс** {ptr→[_,_], len=1, cap=2}
→ это значение присваивается локальной копии a в doSomething
→ исходный слайс s в main остаётся неизменённым {ptr→[_,_], len=0, cap=2}
4. return из doSomething
→ возвращаемое значение выбрасывается
→ локальная переменная a удаляется
5. main: fmt.Println(s)
→ s всё ещё {ptr→[_,_], len=0, cap=2}
→ выводит пустой слайс: []
Визуализация памяти
Данные в памяти (на куче):
┌─────┬─────┐
│ ? │ ? │ capacity=2, но len=0
└─────┴─────┘
Структура слайса s в main:
┌──────┬─────┬─────┐
│ ptr │ 0 │ 2 │ len=0, cap=2
└──────┴─────┴─────┘
↓
данные
После append в doSomething (локально):
┌──────┬─────┬─────┐
│ ptr │ 1 │ 2 │ len=1, cap=2 (новый слайс)
└──────┴─────┴─────┘
Но s в main остаётся:
┌──────┬─────┬─────┐
│ ptr │ 0 │ 2 │ len=0, cap=2 (не изменился)
└──────┴─────┴─────┘
3. Как изменить код?
Вариант 1: Вернуть слайс из функции (рекомендуется)
func main() {
s := make([]int, 0, 2)
s = doSomething(s) // присваиваем возвращаемое значение
fmt.Println(s) // [1]
}
func doSomething(a []int) []int {
a = append(a, 1)
return a // возвращаем обновлённый слайс
}
Вариант 2: Передать указатель на слайс
func main() {
s := make([]int, 0, 2)
doSomething(&s) // передаём указатель на слайс
fmt.Println(s) // [1]
}
func doSomething(a *[]int) {
*a = append(*a, 1) // разыменовываем и модифицируем
}
Вариант 3: Передать слайс с данными (если capacity достаточен)
func main() {
s := make([]int, 0, 2)
a := append(s, 1) // использовать append напрямую
fmt.Println(a) // [1]
}
Почему первый вариант предпочтительнее?
- Явность: читателю понятно, что функция может изменить слайс
- Простота: не нужны указатели, нет разыменования
- Go-подход: стандартный способ для слайсов
- Читаемость:
s = f(s)— явная семантика
// ✅ Go-way: явное возвращение
func append3(s []int, vals ...int) []int {
return append(s, vals...)
}
s = append3(s, 1, 2, 3)
Ключевой вывод
Слайсы в Go — это заголовки данных (headers), передаваемые по значению. Если вам нужно модифицировать слайс через границы функции, либо верните новый слайс, либо передайте указатель.
Это частая ошибка новичков, и её понимание — критично для работы с Go.