Перейти к содержимому
Теория · Интерфейсы · урок 3 из 10

Интерфейс Stringer

В прошлом уроке интерфейс был объявлением о вакансии. Своё объявление есть и у пакета fmt: «умеет рассказать о себе строкой». Если тип значения это умеет, fmt печатает значение так, как тип сам о себе расскажет.

Начнём с типа, который пока этого не умеет. Объявление type Celsius float64 создаёт новый тип для температуры. Его базовый тип — float64: значения и арифметика у них общие. Но fmt.Println печатает температуру голым числом, без единиц:

GO
package main

import "fmt"

type Celsius float64

func main() {
    t := Celsius(21.5)
    fmt.Println(t, t+1) // 21.5 22.5
}

Глядя на 21.5, не скажешь, градусы это, метры или рубли. Хочется, чтобы программа печатала 21.5°C. Методы можно объявить для Celsius, а для float64 — нет: этот тип объявлен не в вашем пакете. Именно метод с особым именем меняет то, как fmt печатает температуру.

Интерфейс fmt.Stringer

Пакет fmt объявляет интерфейс Stringer с одним методом String() string. Перед печатью значения fmt.Println действует так:

  1. Проверяет, реализует ли тип значения интерфейс Stringer.
  2. Если реализует, печатает строку, которую вернул String, а не само значение.
  3. Если метода нет, печатает значение по правилам его базового типа.

Так же поступают fmt.Print, fmt.Printf со знаком %v и другие функции пакета.

Тип реализует Stringer неявно, как любой другой интерфейс. Достаточно метода, у которого:

  • имя String;
  • нет параметров;
  • результат типа string.

Метод String

Метод String для Celsius собирает строку из числа и знака градуса:

GO
func (c Celsius) String() string {
    return fmt.Sprintf("%v°C", float64(c))
}

Разберём тело метода:

  • float64(c) превращает температуру в обычное число;
  • %v подставляет это число в шаблон;
  • за числом идут знаки °C.

Раздел о частой ошибке покажет, зачем здесь преобразование.

Строка без печати

Метод String должен вернуть строку, а не напечатать её. Для этого в fmt есть функции с буквой S в начале имени. Разберём две из них.

fmt.Sprintf

Функция fmt.Sprintf работает как fmt.Printf, но ничего не печатает: готовый текст она возвращает строкой. Шаблон у неё тот же, что у fmt.Printf: %v подставляет значение, а %d — целое число. Её берут, когда значение нужно вставить в середину текста.

fmt.Sprint

Так же fmt.Sprint повторяет fmt.Print: шаблона у неё нет, а аргументы она превращает в строку по правилам fmt.Print. Запись fmt.Sprint(float64(c)) + "°C" даёт ту же строку, что и шаблон "%v°C".

Печать температуры

Теперь fmt печатает температуру с единицами:

GO
t := Celsius(21.5)
fmt.Println(t, t+1)     // 21.5°C 22.5°C
fmt.Println(float64(t)) // 21.5
s := fmt.Sprint(float64(t)) + " degrees"
fmt.Println(s) // 21.5 degrees

Давайте разберём пример по строкам:

  1. t := Celsius(21.5) создаёт температуру.
  2. Первый Println печатает 21.5°C 22.5°C: сумма t+1 сохраняет тип Celsius и тоже печатается через String.
  3. Преобразование float64(t) во втором Println даёт обычное число типа float64. Метода String у этого типа нет, поэтому fmt печатает его как число: 21.5.
  4. Строка с fmt.Sprint собирает текст 21.5 degrees.
  5. Последний Println печатает эту строку.

Здесь картинку с вакансией нужно уточнить. Умение принадлежит не самому числу, а его типу. Преобразование меняет тип значения, и вместе с типом пропадают его методы: число то же, а рассказать о себе оно уже не может.

Частая ошибка

Внутри String значение передали в fmt.Sprint без преобразования типа:

GO
func (c Celsius) String() string {
    return fmt.Sprint(c) + "°C" // ✗
}
TEXT
fatal error: stack overflow

Программа компилируется, но падает при первой печати температуры. fmt.Sprint(c) видит, что c реализует Stringer, и вызывает c.String(), а тот снова вызывает fmt.Sprint(c). Рекурсия не кончается, пока не переполнится стек. Внутри String передавайте в fmt значение базового типа: у float64(c) метода String нет, и круг разрывается.

Главное

  • Тип с методом String() string реализует fmt.Stringer, и fmt печатает его значения через этот метод.
  • fmt.Sprintf и fmt.Sprint не печатают, а возвращают готовую строку.
  • Преобразование float64(t) убирает методы типа, и fmt печатает голое число.
  • Внутри String передавайте в fmt значение базового типа, иначе рекурсия переполнит стек.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом