Во-первых, сообщение в блоге, на которое вы ссылались и взяли пример, appError
- это , а не и error
. Это обертка, которая переносчиков значения ошибки и другой связанной информации, используемой реализацией примеров, не раскрывается, и ни appError
, ни *appError
никогда не используются как error
значение.
Так что приведенный вами пример не имеет никакого отношения к вашему актуальному вопросу. Но чтобы ответить на вопрос в заголовке:
В целом, последовательность может быть причиной. Если тип имеет много методов, а некоторым требуется получатель указателя (например, потому что они изменяют значение), часто бывает полезно объявить все методы с получателем указателя, поэтому не возникает путаницы относительно наборов методов типа и тип указателя.
Ответ относительно реализаций error
: когда вы используете значение struct
для реализации значения error
, опасно использовать не указатель для реализации интерфейса error
. Почему это так?
Потому что error
- это интерфейс. И значения интерфейса сопоставимы . И они сравниваются путем сравнения значений, которые они переносят. И вы получите другой результат сравнения, основанный на том, какие значения / типы заключены в них! Потому что, если вы храните в них указатели, значения ошибок будут равны, если они хранят один и тот же указатель. И если вы храните в них не указатели (структуры), они равны, если значения структуры равны.
Чтобы уточнить это и показать пример:
Стандартная библиотека имеет пакет errors
. Вы можете создавать значения ошибок из значений string
, используя функцию errors.New()
. Если вы посмотрите на его реализацию (errors/errors.go
), все просто:
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
Реализация возвращает указатель на очень простое значение структуры. Это так, что если вы создадите 2 значения ошибки с одинаковым значением string
, они не будут равны:
e1 := errors.New("hey")
e2 := errors.New("hey")
fmt.Println(e1, e2, e1 == e2)
Выход:
hey hey false
Это намеренно.
Теперь, если вы вернете не указатель:
func New(text string) error {
return errorString{text}
}
type errorString struct {
s string
}
func (e errorString) Error() string {
return e.s
}
2 значения ошибок с одинаковыми string
будут равны:
e1 = New("hey")
e2 = New("hey")
fmt.Println(e1, e2, e1 == e2)
Выход:
hey hey true
Попробуйте примеры на Go Playground .
Яркий пример, почему это важно: посмотрите на значение ошибки, хранящееся в переменной io.EOF
:
var EOF = errors.New("EOF")
Ожидается, что реализации io.Reader
вернут это конкретное значение ошибки в конец сигнала ввода. Таким образом, вы можете спокойно сравнить ошибку, возвращаемую Reader.Read()
с io.EOF
, чтобы определить, достигнут ли конец ввода. Вы можете быть уверены, что если они иногда возвращают пользовательские ошибки, они никогда не будут равны io.EOF
, это то, что errors.New()
гарантирует (потому что он возвращает указатель на неэкспортированное значение структуры).