Home > other >  Handle pointer to nil pointer in Go
Handle pointer to nil pointer in Go

Time:09-02

Consider following simplified example:

package main

import (
    "fmt"
)

type IMessenger interface {
    Message()
}

type TMyMessenger struct {
}
func (m TMyMessenger) Message()  {}

func MessengerFactory() IMessenger {
    return getInternalMessengerVariant()
}

func getInternalMessengerVariant() *TMyMessenger {
    return nil
}

func main() {
    e := MessengerFactory()

    fmt.Println(" e == nil", e == nil)  // *TMyMessenger(nil)

    if e != nil {
        e.Message()
    }
}

And it's output:

e == nil false

panic: runtime error: invalid memory address or nil pointer dereference

Question 1: Is there an idiomatic Go way to check if e points to a nil pointer?

Preferably an inline snippet. Basically make the e != nil to be false even in the example case.

What I have considered:

  • There would not be this issue if getInternalMessengerVariant() would return Interface type instead of concrete pointer, but it requires refactor and may still go undetected and yield itself as a panic at runtime (if e != nil).

    func getInternalMessengerVariant() IMessenger {
        return nil
    }
    
  • Rewrite MessengerFactory() to intercept the internal returns:

    func MessengerFactory() IMessenger {
         if m := getInternalMessengerVariant(); m != nil {
             return m
         }
    
         return nil
    }
    
  • Be very specific on type checking, but what if there are many types:

    if e != nil && e != (*TMyMessenger)(nil) {
        e.Message()
    }
    

CodePudding user response:

This problem exists whenever you return an interface from a function: if the interface contains a typed nil-pointer, interface itself is not nil. There is no easy way to check that.

A good way to deal with this is to return a nil for the interface:

func MessengerFactory() IMessenger {
    x:= getInternalMessengerVariant()
    if x==nil {
        return nil
    }
    return x
}

Then you will not need to check if the return value points to a nil pointer.

CodePudding user response:

Burak Serdar explains well in his answer why if x == nil returns false for you.

But that is not the reason why you get the panic.

Go is happy to invoke a receiver function on a nil pointer, as long as the receiver doesn't dereference the pointer.

This does not panic:

type TMyMessenger struct {
}

func (m *TMyMessenger) Message() {}

func main() {
    var t *TMyMessenger = nil
    t.Message()
}

and that's because you don't dereference the pointer m inside the Message receiver function.

Your example only panics because you have defined the receiver function m on the type TMyMessenger (not a pointer). Because of that, Go will have to dereference the nil pointer to TMyMessenger that is inside the IMessenger interface value, in order to invoke the receiver function.

If you change one line in your code, it will no longer panic:

func (m *TMyMessenger) Message()  {}

(change (m TMyMessenger) to (m *TMyMessenger))

  • Related