在Go语言中处理空指针的指针。

huangapple go评论71阅读模式
英文:

Handle pointer to nil pointer in Go

问题

考虑以下简化的示例:

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()
	}
}

它的输出结果为:

e == nil false

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

问题1:
在Go语言中,有没有一种惯用的方法来检查e是否指向空指针?

最好是一种内联的代码片段。即使在示例情况下,使e != nilfalse

我考虑过的解决方案:

  • 如果getInternalMessengerVariant()返回接口类型而不是具体指针类型,就不会出现这个问题,但这需要进行重构,而且仍然可能在运行时(如果e != nil)引发恐慌。

    func getInternalMessengerVariant() IMessenger {
        return nil
    }
    
  • 重写MessengerFactory()以拦截内部返回:

    func MessengerFactory() IMessenger {
        if m := getInternalMessengerVariant(); m != nil {
            return m
        }
    
        return nil
    }
    
  • 对类型检查非常具体,但如果有很多类型怎么办:

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

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()
    }
    

答案1

得分: 1

每当你从函数中返回一个接口时,就会出现这个问题:如果接口包含一个类型化的空指针,那么接口本身不是空。没有简单的方法来检查这一点。

处理这个问题的一个好方法是为接口返回一个空指针:

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

这样你就不需要检查返回值是否指向一个空指针了。

英文:

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.

答案2

得分: 0

Burak Serdar在他的回答中很好地解释了为什么if x == nil对你返回false

但这不是你出现panic的原因。

只要接收器不对指针进行解引用,Go就可以在nil指针上调用接收器函数。

下面的代码不会引发panic:

type TMyMessenger struct {
}

func (m *TMyMessenger) Message() {}

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

这是因为在Message接收器函数内部没有对指针m进行解引用。

你的示例之所以引发panic,是因为你在类型TMyMessenger上定义了接收器函数m(而不是指针)。因此,为了调用接收器函数,Go将不得不对IMessenger接口值中的nil指针进行解引用,该指针指向TMyMessenger

如果你在代码中更改一行,它将不再引发panic:

func (m *TMyMessenger) Message()  {}

(将(m TMyMessenger)更改为(m *TMyMessenger)

英文:

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))

huangapple
  • 本文由 发表于 2022年9月1日 05:59:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/73562316.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定