Перейти к содержанию

Структуры и интерфейсы

TL;DR

  • struct группирует поля.
  • Методы привязываются через receiver: func (u *User) Foo().
  • Pointer receiver для модификации; value — для read-only мелочей.
  • Интерфейс — implicit, реализация по структуре методов.
  • any (он же interface{}) — может хранить любой тип.

Структуры

type User struct {
    ID   int64
    Name string
    Age  int
}

u := User{ID: 1, Name: "Anna", Age: 30}
u2 := User{1, "Bob", 25}              // позиционно — не рекомендуется

Сравнение: структуры comparable если все поля comparable.

Методы

type Counter struct{ n int }

func (c Counter) Get() int   { return c.n }    // value receiver
func (c *Counter) Inc()      { c.n++ }          // pointer receiver

Правило: не миксуй receiver'ы у одного типа. Либо все pointer, либо все value. Иначе можешь случайно потерять изменения.

Интерфейсы

В Go реализация интерфейса implicit — не нужно объявлять implements.

type Stringer interface {
    String() string
}

type User struct{ Name string }
func (u User) String() string { return u.Name }

var s Stringer = User{Name: "Anna"}   // работает автоматически

Принцип маленьких интерфейсов

В стандартной библиотеке интерфейсы маленькие: io.Reader, io.Writer, io.Closer. Большие интерфейсы → много реализаций ломается.

«Accept interfaces, return structs» — функции принимают интерфейс (минимальный нужный), возвращают конкретные структуры.

Гочча — typed nil

type MyError struct{}
func (e *MyError) Error() string { return "" }

func doSomething() error {
    var p *MyError = nil
    return p
}

err := doSomething()
fmt.Println(err == nil)   // false!

Объяснение: интерфейс хранит (type, value). type = *MyError (≠ nil). err == nil истинно только когда type=nil И value=nil.

Правильно:

func doSomething() error {
    var p *MyError
    if shouldFail() {
        p = &MyError{}
    }
    if p == nil {
        return nil   // явно вернуть nil, не *MyError(nil)
    }
    return p
}

📖 Связанные задачи: must-solve-100 → C-009..C-012.