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

В чем разница между передачей slice в метод по ссылке и по значению?

2.3 Middle🔥 112 комментариев
#Основы Go

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Разница между передачей slice по значению и по ссылке в Go

Slice (срез) в Go — это структура данных, которая предоставляет удобный интерфейс для работы с последовательностями элементов. При передаче slice в функцию важно понимать, как работает механизм передачи, поскольку он имеет особенности, отличающиеся от других языков.

Структура slice в памяти

Срез в Go является легковесной структурой данных, которая содержит три компонента:

  • Pointer — указатель на первый элемент массива (underlying array)
  • Length — текущая длина среза
  • Capacity — максимальная емкость среза
// Пример структуры среза (не фактический код, но концепция)
type slice struct {
    ptr *byte  // указатель на массив
    len int    // длина
    cap int    // емкость
}

Передача по значению

Когда вы передаете slice в функцию по значению (обычный способ в Go), происходит копирование структуры среза (pointer, len, cap), но не копируется сам underlying array (базовый массив).

func modifySlice(s []int) {
    s[0] = 100  // Изменение отразится на оригинале
    s = append(s, 4)  // Это может не отразиться на оригинале
}

func main() {
    original := []int{1, 2, 3}
    modifySlice(original)
    fmt.Println(original)  // [100, 2, 3]
}

Ключевые моменты при передаче по значению:

  • Изменения элементов внутри функции видны вне функции, поскольку pointer копируется и ссылается на тот же массив.
  • Операции, меняющие len/cap (например, append) могут создать новый базовый массив, если capacity недостаточно. В этом случае изменения не будут видны в оригинальном срезе.
  • Срез внутри функции становится независимой структурой с собственными len/cap, но с shared array до возможного reallocation.

Передача по ссылке

Передача по ссылке означает передачу pointer на slice (*[]int). Это позволяет изменять саму структуру среза (pointer, len, cap) внутри функции.

func modifySliceByReference(s *[]int) {
    (*s)[0] = 100  // Изменение элемента
    *s = append(*s, 4)  // Append изменяет оригинальный срез
}

func main() {
    original := []int{1, 2, 3}
    modifySliceByReference(&original)
    fmt.Println(original)  // [100, 2, |
    // len и cap могут быть изменены
}

Сравнение подходов

КритерийПо значению ([]int)По ссылке (*[]int)
Изменение элементовВидно вне функцииВидно вне функции
Изменение len/cap через appendМожет не отразиться (при реаллокации)Всегда отражается
Переопределение всего срезаНе влияет на оригиналВлияет на оригинал
ПроизводительностьКопирование 3 слов (ptr, len, cap)Копирование 1 слова (pointer)
Стиль GoБолее распространенИспользуется для особых случаев

Когда использовать каждый подход

По значению (рекомендуется в большинстве случаев):

  • Когда нужно только читать или модифицировать элементы без изменения длины.
  • При работе с функциями, которые не должны изменять структуру среза (например, поиск, фильтрация).
  • Для лучшей прозрачности кода — явное разделение данных.

По ссылке (использовать осторожно):

  • Когда функция должна расширять или сокращать срез и изменения должны быть вины вызывающей стороне.
  • При реализации функций типа append, resize, которые меняют структуру среза.
  • Когда нужно переопределить весь срез внутри функции (например, очистить s = nil).

Примеры с пояснениями

Передача по значению с ограничением:

func appendWithoutEffect(s []int, value int) {
    s = append(s, value)  // Может создать новый массив
}

func main() {
    s := make([]int, 0, 3)  // Capacity = 3
    appendWithoutEffect(s, 1)
    fmt.Println(s)  // [], потому что len не изменился вне функции
}

Передача по ссылке для полного контроля:

func safelyAppend(s *[]int, values ...int) {
    *s = append(*s, values...)  // Изменяет оригинальный срез
}

func main() {
    s := []int{1, 2}
    safelyAppend(&s, 3, 4)
    fmt.Println(s)  // [1, 2, 3, 4]
}

Итог

В Go срез по умолчанию передается по значению, но это значение включает pointer на массив, поэтому модификация элементов возможна. Разница проявляется при операциях, изменяющих структуру среза (len/cap). Для полного контроля над срезом внутри функции нужно передавать pointer на срез. Выбор метода зависит от требуемого поведения: если нужно изменить только элементы — передача по значению достаточна; если нужно изменять размер или переопределять срез — используйте передачу по ссылке.

В чем разница между передачей slice в метод по ссылке и по значению? | PrepBro