Обработка ошибок Go, утверждение типа и сетевой пакет

Я учусь и пытаюсь понять, как получить более подробную информацию об ошибках из общего типа ошибки. Пример, который я буду использовать, - это сетевой пакет, в частности функция DialTimeout.

Подпись

func DialTimeout(network, address string, timeout time.Duration) (Conn, error)

Тип error определяет только функцию Error() string. Если я хочу выяснить, почему DialTimeout не удалось, как я могу получить эту информацию? Я узнал, что могу использовать утверждение типа для получения конкретной ошибки net.Error:

con, err := net.DialTimeout("tcp", net.JoinHostPort(address, "22"),
    time.Duration(5) * time.Second)
if err != nil {
    netErr, ok := err.(net.Error)
    if ok && netErr.Timeout() {
        // ...
    } 
} 

но это только говорит мне, был ли у меня тайм-аут. Например, скажем, я хотел различать отказанное соединение и никакой маршрут к хосту. Как я могу это сделать?

Возможно, DialTimeout слишком высокоуровневый, чтобы дать мне такую ​​деталь, но даже глядя на syscall.Connect, я не вижу как получить конкретную ошибку. Он просто говорит, что возвращает общий тип ошибки. Сравните это с Posix connect, который сообщит мне, почему он не прошел с различными кодами возврата.

Мой общий вопрос: как я должен вытащить данные об ошибке из общего типа error, если документы golang не говорят мне, какие ошибки могут быть возвращены?

Ответ 1

Большинство сетевых операций возвращают *OpError, в котором содержится подробная информация об ошибке и реализует интерфейс net.Error. Поэтому для большинства случаев использования достаточно использовать net.Error как вы уже это делали.

Но для вашего случая вы хотите утверждать, что возвращаемая ошибка была *net.OpError и используйте внутреннюю ошибку:

if err != nil {
    if oerr, ok := err.(*OpError); ok {
        // Do something with oerr.Err
    }
}

Как только вы это сделаете, вы находитесь в зоне зависимости от платформы как системные вызовы под Linux могут по-разному терпеть неудачу под Windows. Для Linux вы бы сделали что-то вроде этого:

if oerr.Err == syscall.ECONNREFUSED {
    // Connection was refused :(
}

Пакет syscall содержит важные константы ошибок для вашей платформы. К сожалению сайт golang показывает только пакет syscall для Linux amd64. См. здесь для ECONNREFUSED.

Поиск типов

В следующий раз вам интересно, что фактически возвращается какой-то функцией, и вы не можете сделать головами и хвостами, попробуйте использовать формат %#v, указанный в fmt.Printf (и друзьях):

 fmt.Printf("%#v\n", err)
 // &net.OpError{Op:"dial", Net:"tcp", Addr:(*net.TCPAddr)(0xc20006d390), Err:0x6f}

Он напечатает подробную информацию о типе и, как правило, весьма полезен.