Классическая ловушка: метод String() вызывает fmt.Sprintf с самим собой как аргументом:
type User struct {
Name string
Age int
}
// Бесконечная рекурсия!
func (u User) String() string {
return fmt.Sprintf("User: %s", u) // fmt вызовет u.String() снова!
}
// Правильно - форматировать поля явно:
func (u User) String() string {
return fmt.Sprintf("User: %s, %d", u.Name, u.Age)
}
// Или привести к другому типу:
type userAlias User
func (u User) String() string {
return fmt.Sprintf("%v", userAlias(u)) // userAlias не имеет String()
}
Глаголы %s, %v, %q - все вызывают String() если тип реализует fmt.Stringer. Аналогичная проблема с Error() и fmt.Errorf.