📜 ⬆️ ⬇️

Nil is not always nil

Nil is not always nil


"What? What is it all written here?" you ask. Now everything will decompose.


When I started to learn a language, I did not think that I would go into this narrow case. This is also not rational as changing the iterated collection.


For example:

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

WAT!


What is the 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 ... } 

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


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


The foo function returns nil of the * os.PathError type, we compare the result with nil of the nil type, whence their inequality follows.


Perhaps many people knew about this, but few people think how to get into this 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 it is necessary through the notification service.
Inside the service, the Error method is called, and since our value is nil, we get a panic.


What to do?

Return interface is strictly interface type.


In the event of an error, the type of error.



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

To my surprise, this technique does not work either.


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



 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 let's be honest, we cannot afford to check all types of errors that we will transmit.


These 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 } 

At first, we have declared a variable of type error, as it turns out with the value and type nil.
And before passing our type and value of this variable, we check our type and its value for nil.


This will allow us not to fall with panic.


At last

You can go even further and implement an "optional" error for 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 notes Go wiki code review a note in the topic about the interface:


I will note that the dances given by me above are not about that.


My notes allow you not to fall down with 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.


Links

go-internals


I

LinkedIn
Telegram
Twitter
Github


')

Source: https://habr.com/ru/post/449714/


All Articles