Структуры, указатели, методы и интерфейсы

Структуры и указатели: объявление, инициализация и передача по адресу

Содержание курса

Передача struct по значению и по указателю: видимость изменений в вызывающем коде

Возьмём конкретный пример. Есть структура и две функции:

type Point struct {
    X int
    Y int
}

func scale(p Point, f int) {
    p.X *= f
    p.Y *= f
}

func scalePtr(p *Point, f int) {
    p.X *= f
    p.Y *= f
}

Что произойдёт в каждом случае:

func main() {
    a := Point{X: 3, Y: 4}

    scale(a, 10)
    fmt.Println(a) // {3 4} — ничего не изменилось

    scalePtr(&a, 10)
    fmt.Println(a) // {30 40} — изменилось
}

Почему так? При вызове scale(a, 10) Go копирует всё содержимое a в новую переменную p внутри функции. p.X и p.Y — это отдельные поля в отдельной области памяти. Функция умножает их, но оригинальный a об этом ничего не знает.

При вызове scalePtr(&a, 10) функция получает адрес a. Когда внутри функции выполняется p.X *= f, это разворачивается в изменение (*p).X — то есть поля того самого a в вызывающем коде.

Это фундаментальное отличие от срезов, с которыми вы работали раньше. Срез — это заголовок с указателем на массив; копирование заголовка оставляет общий массив. Структура с числовыми полями копируется полностью: после копирования нет никакой связи между оригиналом и копией.

Практическое правило для структур со скалярными полями: если функция должна изменить данные и вы хотите видеть изменения снаружи — передавайте *T. Если функция только читает или должна работать с независимой копией — передавайте T.