> Является ли новый слайс после добавления элементов копией или ссылкой в Go (Go)

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

Компании: Ozon

Стек: Go

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

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

Новый слайс после добавления элементов - это новая структура данных (slice header), но с возможным общим underlying array. Если容量 позволяет, append переиспользует существующий массив, и новый слайс ссылается на те же данные. Если容量 не хватает - выделяется новый массив, и слайсы становятся полностью независимыми. Важно: сам header всегда новый, но данные могут быть общими.

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

Слайс в Go - это структура из трёх полей: указатель на массив, длина и ёмкость. При вызове append происходит следующее:

  1. Проверяется, достаточно ли ёмкости (cap) для добавления элементов.
  2. Если len + n <= cap - элементы записываются в существующий массив, возвращается новый header с увеличенной длиной. Указатель на массив остаётся тем же.
  3. Если len + n > cap - выделяется новый массив большего размера (обычно в два раза), все элементы копируются, затем добавляются новые. Возвращается header с новым указателем.

Ключевой момент: append всегда возвращает новый slice header, даже если underlying array тот же. Поэтому присваивание результата обязательно - иначе вы потеряете обновлённую длину.

При этом, если массив общий, изменения через один слайс будут видны через другой (до тех пор, пока не произойдёт реаллокация). Это частая причина багов.

На практике

На практике это означает:

  • Всегда присваивайте результат append обратно переменной: s = append(s, x).
  • Если нужно гарантировать независимость - используйте copy в новый слайс с достаточной ёмкостью.
  • При передаче слайсов в функции и последующем append внутри - изменения могут не отразиться на вызывающей стороне, если произошла реаллокация.
  • Для безопасного добавления в общий слайс из нескольких горутин нужна синхронизация - сам append не потокобезопасен.

Пример кода

GO
package main
import "fmt"
func main() {
// Случай 1: ёмкости достаточно - общий массив
s1 := make([]int, 3, 5)
s1[0], s1[1], s1[2] = 1, 2, 3
s2 := append(s1, 4)
fmt.Printf("s1: len=%d cap=%d ptr=%p\n", len(s1), cap(s1), s1)
fmt.Printf("s2: len=%d cap=%d ptr=%p\n", len(s2), cap(s2), s2)
s2[0] = 100
fmt.Println("s1[0] =", s1[0]) // 100 - массив общий
// Случай 2: ёмкости не хватает - новый массив
s3 := []int{1, 2, 3}
s4 := append(s3, 4, 5, 6)
fmt.Printf("s3: len=%d cap=%d ptr=%p\n", len(s3), cap(s3), s3)
fmt.Printf("s4: len=%d cap=%d ptr=%p\n", len(s4), cap(s4), s4)
s4[0] = 200
fmt.Println("s3[0] =", s3[0]) // 1 - массивы разные
}

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

Начните с чёткого разделения: slice header всегда новый, а вот underlying array может быть общим. Объясните механизм проверки ёмкости и реаллокации. Приведите пример, когда изменение через один слайс влияет на другой, и когда - нет. Упомяните, что реаллокация происходит при превышении cap, и что размер нового массива зависит от реализации (обычно удвоение для малых, но может быть и другой стратегией для больших). Подчеркните важность присваивания результата append.

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

Интервьюер проверяет:

  • Понимание внутреннего устройства слайса (header, указатель, len, cap).
  • Понимание семантики append и случая реаллокации.
  • Осознание последствий общего underlying array (aliasing).
  • Умение предвидеть поведение кода с append в разных сценариях.
  • Знание практических рекомендаций (присваивание результата, copy для независимости).

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

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

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

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