Перейти к основному содержимому
Модуль 6 · Урок 4 из 529/50 в курсе~10 min
Уроки модуля (4/5)

Интерфейс Стрингера

fmt.Stringer, вероятно, является наиболее часто используемым стандартным интерфейсом во всех Стандартная библиотека Go. Это идеальная демонстрация силы небольших интерфейсов.

Определение

Go
type Stringer interface {
    String() string
}

Одна подпись. Любой тип, определяющий String() string, удовлетворяет это. И fmt распознает этот интерфейс: когда вы передаете значение fmt.Print*, если он реализует Stringer, результат String() используется вместо представления по умолчанию.

Пример

Go
type Point struct{ X, Y int }

func (p Point) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

fmt.Println(Point{1, 2})              // (1,2)
fmt.Printf("%v %s\n", Point{3, 4}, Point{5, 6})
// (3,4) (5,6)

То же значение печатается с пользовательским форматированием в %v, %s, Println, Println, Sprint, log и т. д. И все это путем определения одного метода.

Когда это имеет смысл

  • Типы, моделирующие домен (идентификаторы, коды, координаты, перечисления).
  • Ошибки с удобочитаемым представлением (см. следующий урок по error).
  • Типы, которые вы часто регистрируете и для которых вам нужен фиксированный формат.

Не для всего: если тип простой и представление по умолчанию все в порядке, не добавляйте шума.

Типы перечислений: общий шаблон с iota

Go
type Status int

const (
    OK Status = iota
    WARN
    ERR
)

func (s Status) String() string {
    switch s {
    case OK:   return "OK"
    case WARN: return "WARN"
    case ERR:  return "ERR"
    default:   return "UNKNOWN"
    }
}

fmt.Println(OK, WARN, ERR)   // OK WARN ERR

Без String() fmt.Println(OK) напечатал бы 0. С String(), он печатает символическое имя. stringer инструмент автоматически генерирует этот шаблон.

Остерегайтесь бесконечной рекурсии

Go
type T int
func (t T) String() string {
    return fmt.Sprintf("%d", int(t))  // OK: cast to int breaks the recursion
}

Стрингер на приемнике указателя

Если String() имеет приемник указателя, только *T удовлетворяет Stringer. Используя fmt.Println(t) (значение), Go просматривает набор методов T, не находит его и использует формат по умолчанию. Чтобы этого избежать, значение получатель обычно используется для String() (это «представление»; он не изменить что-либо).

Попробуйте это

Упражнение#go.m6.l4.e1
Попыток: 0Загрузка…

Реализуйте String() для Point, чтобы он возвращал (X,Y).

Загрузка редактора…
Показать подсказку

Используйте fmt.Sprintf для форматирования полей.

Решение доступно после 3 попыток

Упражнение#go.m6.l4.e2
Попыток: 0Загрузка…

Реализуйте String() для типа Status (int): 0 → «ОК», 1 → «WARN», в противном случае → «ERR».

Загрузка редактора…
Показать подсказку

Включите значение получателя; не забудьте указать значение по умолчанию.

Решение доступно после 3 попыток

Quiz#go.m6.l4.e3
Готово

Какая подпись должна быть у метода, чтобы удовлетворить fmt.Stringer?

Go
func (x T) ??? {}
Варианты ответа

Резюме

  • fmt.Stringer = interface { String() string }.
  • Реализация меняет способ представления типа fmt.Print*.
  • Идиоматика для перечислений (iota), типов доменов, кодов/идентификаторов.
  • Никогда не вызывайте fmt.Sprintf("%s", t) на t внутри String() (рекурсия).
  • Соглашение: приемник значения для String() (без мутаций, позволяет избежать «сюрпризов набора методов»).