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

Что выведет код? Pointer vs Value receiver

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

Условие

Определите, что произойдет при компиляции и выполнении следующего кода:

package main

import "fmt"

type Bar interface {
    Get() string
}

func print(i Bar) {
    fmt.Println(i.Get())
}

type Foo struct {
    f string
}

func (foo *Foo) Get() string {
    return foo.f
}

func main() {
    foo := Foo{"hello"}
    print(foo)
}

Вопросы

  1. Скомпилируется ли код?
  2. Если нет, какая будет ошибка?
  3. Как исправить код?

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

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

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

Решение

Это задача про pointer vs value receivers и interface в Go. Классический подвох, который показывает важность понимания, как методы привязываются к типам.

Ответ на вопросы

1. Скомпилируется ли код?

НЕТ, код НЕ скомпилируется.

2. Какая будет ошибка?

compilation error:
Foo does not implement Bar (Get method has pointer receiver)

Ошибка компилятора говорит, что Foo не реализует интерфейс Bar, потому что метод Get() определён с pointer receiver *Foo, а не value receiver Foo.

3. Как исправить код?

Есть три варианта, каждый с разными implications:

Вариант 1: Передать указатель (рекомендуется)

func main() {
    foo := Foo{"hello"}
    print(&foo)  // передаём указатель вместо значения
}

Плюсы:

  • Не меняем определение метода
  • Явно показываем, что функция работает с указателями

Минусы:

  • Нужно помнить о передаче указателя

Вариант 2: Изменить receiver на value receiver

func (foo Foo) Get() string {
    return foo.f
}

Плюсы:

  • Работает с обоими: значениями и указателями
  • Не нужно помнить о &foo

Минусы:

  • Если структура большая, копируется целиком
  • Если нужно модифицировать, не сработает

Вариант 3: Изменить интерфейс

type Bar interface {
    Get() *string  // или другой способ
}

НО это не очень хорошее решение, так как нарушает дизайн.

Почему это происходит? (Важно!)

В Go есть правило для методов:

Value receiver func (t T) Method() реализует методы для:

  • T (значение)
  • *T (указатель, автоматическое разыменование)

Pointer receiver func (t *T) Method() реализует методы для:

  • *T (указатель)
  • T НЕ реализует (поправить: Go НЕ автоматически берёт адрес)
Type:        Value receiver          Pointer receiver
─────────────────────────────────────────────────────
t T          ✅ реализует           ❌ НЕ реализует
*T           ✅ реализует            ✅ реализует

Пошаговый анализ кода

foo := Foo{"hello"}              // foo тип Foo (значение)
print(foo)                       // функция ожидает Bar

// Интерфейс Bar требует метод Get() string
// У Foo есть метод:
// func (foo *Foo) Get() string  // для *Foo

// Foo (значение) НЕ имеет метода Get()
// Потому что метод определён с pointer receiver
// → Ошибка компилятора

Визуализация

Интерфейс Bar:
┌──────────────────┐
│ Get() string     │
└──────────────────┘
       ↓ implements

Тип Foo с pointer receiver:
┌──────────────────┐
│ *Foo             │  ← только для указателя!
│ Get() string ✅  │
└──────────────────┘

Тип Foo (значение):
┌──────────────────┐
│ Foo              │  ← НЕ имеет метода Get()
│ (no Get) ❌      │
└──────────────────┘

Почему Go так делает?

Это сделано умышленно:

  1. Безопасность: если у вас есть foo := Foo{...}, вы можете потерять изменения, если метод их делал:

    foo := Foo{"hello"}
    foo.Modify()      // если Modify() с pointer receiver
    // Изменения потеряются, потому что копия модифицируется
    
  2. Явность: разработчик должен явно решить:

    • Нужен ли access к значению или к указателю?
    • Безопасно ли копировать?
  3. Производительность: для больших структур копирование дорого

Правильный подход (лучшие практики)

Value receiver:

func (foo Foo) String() string {
    return fmt.Sprintf("Foo{%s}", foo.f)
}

// ✅ Работает с Foo и *Foo

Pointer receiver:

func (foo *Foo) Modify(s string) {
    foo.f = s
}

// ✅ Работает с *Foo
// ❌ Не работает с Foo без &

Лучшая практика: всегда передавайте указатели в интерфейсы

Если метод — pointer receiver, определяйте функцию с интерфейсом так:

// ✅ Хорошо
func process(i *Bar) {
    i.Get()
}

// ❌ Плохо
func process(i Bar) {
    i.Get()  // ошибка для pointer receivers
}

Исправленный код (Вариант 1 — самый правильный)

package main

import "fmt"

type Bar interface {
    Get() string
}

func print(i Bar) {
    fmt.Println(i.Get())
}

type Foo struct {
    f string
}

func (foo *Foo) Get() string {
    return foo.f
}

func main() {
    foo := Foo{"hello"}
    print(&foo)  // передаём указатель
    // Вывод: hello
}

Исправленный код (Вариант 2 — если нет модификаций)

func (foo Foo) Get() string {  // value receiver
    return foo.f
}

func main() {
    foo := Foo{"hello"}
    print(foo)  // теперь работает
    // Вывод: hello
}

Ключевой вывод

Pointer receiver реализует интерфейс только для типа *T, а не для T. Если вам нужно использовать значение, либо измените receiver на value, либо передавайте указатель.

Это частая ошибка на собеседованиях и демонстрирует глубокое понимание системы методов в Go.

Что выведет код? Pointer vs Value receiver | PrepBro