> Как сделать так, чтобы изменения слайса в функции были видны в вызывающей функции (Go)

Уровень: middle · Роль: backend · Категория: Технические вопросы

Компании: Ozon, Black Wall Group (BWG)

Стек: Go

> Пример ответа

Короткий ответ

Слайс в Go - это структура из трёх полей: указатель на массив, длина и ёмкость. При передаче в функцию копируется заголовок слайса, но не сам массив. Чтобы изменения были видны в вызывающей функции, нужно либо передавать указатель на слайс (*[]T), либо возвращать изменённый слайс из функции и присваивать его в вызывающей стороне. Если меняются только элементы внутри существующего массива - изменения видны без дополнительных действий.

Подробное объяснение

Слайс - это ссылочный тип, но передаётся по значению. Копируется заголовок: указатель, len, cap. Поэтому:

  • Изменение элементов s[i] = x - видно, так как указатель ведёт на тот же массив.
  • Добавление через append может пересоздать массив, если ёмкости не хватает - тогда исходный слайс не изменится.
  • Изменение длины или ёмкости внутри функции не влияет на внешний слайс.

Чтобы гарантированно применить изменения структуры слайса (добавление, удаление, перестановка), используйте:

  1. Указатель на слайс: func f(s *[]int) - внутри работаете с *s.
  2. Возврат нового слайса: func f(s []int) []int - вызывающий присваивает результат.

Второй вариант более идиоматичен для Go, так как избегает мутаций и упрощает чтение кода.

На практике

  • Если функция только читает или меняет элементы - передавайте слайс по значению.
  • Если функция добавляет или удаляет элементы - возвращайте новый слайс.
  • Указатель на слайс оправдан, когда нужно менять сам слайс в нескольких местах или в цикле, но это менее читаемо.
  • Помните про append: он возвращает новый слайс, поэтому без присваивания результат теряется.

Пример кода

GO
// Изменение элементов - работает без возврата
func setFirst(s []int, v int) {
s[0] = v
}
// Добавление - нужно вернуть новый слайс
func appendValue(s []int, v int) []int {
return append(s, v)
}
// Через указатель - тоже работает, но менее идиоматично
func appendValuePtr(s *[]int, v int) {
*s = append(*s, v)
}
func main() {
a := []int{1, 2, 3}
setFirst(a, 10) // a = [10, 2, 3]
a = appendValue(a, 4) // a = [10, 2, 3, 4]
b := []int{1, 2}
appendValuePtr(&b, 5) // b = [1, 2, 5]
}

Как отвечать на собеседовании

Начните с того, что слайс - это структура с указателем на массив. Объясните разницу между изменением элементов и изменением структуры слайса. Приведите оба способа: указатель и возврат значения. Подчеркните, что возврат - более идиоматичный подход. Если спросят про производительность - упомяните, что копирование заголовка слайса дешёвое (3 слова), а указатель на слайс добавляет лишний уровень косвенности.

Что проверяет интервьюер

  • Понимание внутреннего устройства слайса (header, указатель, len, cap).
  • Знание поведения append и случаев переаллокации массива.
  • Умение выбирать между передачей по значению и по указателю.
  • Понимание идиоматичности Go-кода.

Типичные ошибки

  • Утверждение, что слайс передаётся по ссылке - это неверно, передаётся копия заголовка.
  • Забывают, что append может вернуть новый слайс, и не присваивают результат.
  • Передают указатель на слайс там, где достаточно возврата значения - это усложняет код.
  • Путают изменение элемента (s[i] = x) с изменением самого слайса (s = append(s, x)).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью