Nil is not always nil

Nil is not always nil


"What? What is written here?" you ask. Now I'll put it all down.


When I started to learn the language, I didn’t think that I would come to this narrow case. It is also not rational to modify an iterable collection.


For example:

func Foo() error {
    var err *os.PathError = nil
    return err
}
func main() {
    err := Foo()
    fmt.Println(err)           // 
    fmt.Println(err == nil) // false
}

WAT!


What is an interface?

Go to the go runtime / runtime2.go package file and see:


type itab struct { // 40 bytes on a 64bit arch
    inter *interfacetype
    _type *_type
    ...
}

An interface stores the type of interface and the type of value itself.


The value of any interface, not just error, is nil in the case when the AND values ​​and type are nil.


The Foo function returns nil of type * os.PathError, we compare the result with nil of type nil, from which their inequality follows.


Perhaps many knew about this, but few people think how to get into it in practice.


My example

type Response struct {
    Result ResponseResult    `json:"result,omitempty"`
    Error  *ResponseError    `json:"error,omitempty"`
}
type ResponseError struct {
    Message string `json:"message"`
}
func (e *ResponseError) Error() string {
    return e.Message
}
...
func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
    var res handlers.Response
    _ = json.Unmarshal(body, &res)
    if res.Error == nil {
        return
    }
    return s.NotifyError(err)
}

Response always has a result or error.


If there is an error, we send it where necessary through the notification service.
Inside the service, the Error method is called, and since our value is nil, we get panic.


What to do?

Return an interface strictly of an interface type.


In case of an error - type of error.


  • Add a declaration of type error

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
    var res Response
    _ = json.Unmarshal(body, &res)
        var err error = res.Error
    return s.NotifyError(err)
}

This technique, to my surprise, does not work either.


It turns out that when assigning a value to the err variable, we also pass to it the initial information about the type, which is not nil.


  • Let's try to get our source type from the interface type and check its value.

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    ...
        if e, ok := err.(*ResponseError); ok && e == nil {
        return s.NotifyError(err)
    }
        return nil
}

Yes, this technique works.


But to be honest, we cannot afford to check all the types of errors that we will transmit.


It can be all errors from the database driver, all our internal errors and other garbage.


What is the most rational option I see:

func (s *NotificationService) NotifyIfError(w *ResponseWriter) error {
    var err error
        ...
        var res Response
        _ = json.Unmarshal(body, &res)
        if res.Error != nil {
            return s.NotifyError(err)
        }
    return nil
}

First, we declared a variable of type error, as it turns out with the value and type nil.
And before passing our type and value to this variable, let's check our type and its value on nil.


This will allow us not to fall with a panic.


In the end

You can go even further and implement an “optional” error with the Response, OptionalError or ErrorOrNil type, like this:


func (r *Response) ErrorOrNil() error {
    if r.Error == nil {
        return nil
    }
    return r.Error
}

On a note

In the Go wiki notes, the code review is a note in the topic about the interface: Instead return a concrete type and let the consumer mock the producer implementation.


I note that the above dances are not about this.


My notes allow you not to fall into a panic when you know that you want to return interest, and in case of errors, you always want to return the interface.


But if you can afford to return a certain type, return it.


References

go-internals


I

LinkedIn
Telegram
Twitter
Github


Also popular now: