четверг, 21 ноября 2019 г.

Golang puzzlers: возврат ошибки

В постах Golang puzzlers мы рассмотрим различные случаи в Golang, которые могут быть неочевидными на первый взгляд.

Пример этого поста. Дан код:

package main

import (
 "fmt"
)

type myType struct {
 msg string
}

func (m *myType) Error() string {
 return m.msg
}

func test() *myType {
 return nil
}

func main() {
 var err error
 err = test()
 if err != nil {
  fmt.Println("occured error")
  return
 }
 fmt.Println("ok")
}

Вопрос: что выведет данный код?

На первый взгляд все очевидно: задан тип myType, указатель на который реализует интерфейс error. Раз указатель на myType реализует интерфейс ошибки, то кажется что все верно - функция test возвращает указатель на myType, а в функции main возвращаемое значение будет записано в переменную типа error. При запуске программа скомпилируется и кажется, что раз мы возвращаем nil в test, то будет выведено ok, но в консоль выведется occured error!

Начнем разбор - распечатаем переменную err, которая должна быть равна nil:

package main

import (
 "fmt"
)

type myType struct {
 msg string
}

func (m *myType) Error() string {
 return m.msg
}

func test() *myType {
 return nil
}

func main() {
 var err error
 err = test()
 if err != nil {
  fmt.Println("occured error")
  fmt.Println(err)
  return
 }
 fmt.Println("ok")
}

При запуске получим:

occured error
<nil>

Значит err равно nil? В чем подвох? Мы сможем понять это использовав пакет reflect для разбора:

package main

import (
 "fmt"
 "reflect"
)

type myType struct {
 msg string
}

func (m *myType) Error() string {
 return m.msg
}

func test() *myType {
 return nil
}

func main() {
 var err error
 err = test()
 if err != nil {
  fmt.Println("error")
  fmt.Println(err)
  e := reflect.ValueOf(err)
  fmt.Println(e)
  fmt.Println(e.Kind())
  return
 }
 fmt.Println("ok")
 fmt.Println(err)
}

При запуске получим:

error
<nil>
<nil>
ptr

Что получается? В err записывается указатель со значением nil. Это указатель на тип myType, который реализует интерфейс error, поэтому этот указатель можно записать в переменную типа error - компилятор позволит нам это. Но при проверке на nil за nil засчитается только значение записанное по типу интерфейса error - то есть если бы функция test в качестве возвращаемого значения в сигнатуре функции указывала бы интерфейс error, тогда все бы работало как предполагается и в консоль было бы выведено ok.

package main

import (
 "fmt"
)

type myType struct {
 msg string
}

func (m *myType) Error() string {
 return m.msg
}

func test() error {
 return nil
}

func main() {
 var err error
 err = test()
 if err != nil {
  fmt.Println("error")
  return
 }
 fmt.Println("ok")
}

При запуске получим:

ok

Запустить пример в песочнице play.golang.org


Читайте также:


Комментариев нет:

Отправить комментарий