← Назад к вопросам

Что выведет код? Slice и append

1.0 Junior🔥 241 комментариев
#Основы Go

Условие

Определите, что выведет следующий код:

package main

import "fmt"

func main() {
    s := make([]int, 0, 2)
    doSomething(s)
    fmt.Println(s)
}

func doSomething(a []int) {
    a = append(a, 1)
}

Вопросы

  1. Что выведет программа?
  2. Почему?
  3. Как изменить код, чтобы изменения были видны в main?

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Это классическая задача, которая показывает критическое различие между слайсами в 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.