Golang中接收器为nil的问题如何解决

Golang中接收器为nil的问题如何解决 我一定是误解了什么,当我这样写时:

_, err = m.ToolbarRefresh.Connect("clicked", m.FundList.updateFundsValue)

其中 updateFundsValue() 是声明在 fundList 类型上的函数:

func (f *fundList) updateFundsValue() {
 go func() {
         ...
 }()
}

我在运行时得到一个错误:

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

因为在 updateFundsValue() 内部,接收者 fnil。但是当我这样写时,它却能正常工作:

_, err = m.ToolbarRefresh.Connect("clicked", func() {
	m.FundList.updateFundsValue()
})

在这种情况下,m.FundList 有一个值,并且接收者 f 也有一个值。这是为什么呢?第一种方式在其他情况下是可行的,例如:

_, err = m.ToolbarQuit.Connect("clicked", m.shutDown) 

附注:这段代码使用的是 GoTK3(https://github.com/gotk3/gotk3),但我认为这个问题与 GoTK3 无关,因为第二种情况工作正常。当有一个类型 m 指向另一个类型 FundList 的指针时,难道不能使用单行代码吗?这似乎很随机……


更多关于Golang中接收器为nil的问题如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

从语法上讲,没有理由它不应该工作。你在调用 Connect 后检查错误值了吗?在你点击时,mm.FundList 是非 nil 的吗?

更多关于Golang中接收器为nil的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


skillian:

从语法上讲,没有理由它不应该工作。

这很好,我之前从未遇到过这个问题,所以我没想到这里会有任何问题……但确实出现了问题……

skillian:

你在调用 Connect 之后检查错误值了吗?

是的,并且没有错误……

skillian:

在你点击时,mm.FundList 是非空值吗?

它们应该是的。m 是一个指向主窗体的指针,当按钮被点击时该窗体仍然处于打开状态,而 m.FundList 是一个指针,只有在我关闭主窗体时(在 shutdown() 函数中)才会被清空。

无论如何,我认为你的回答——即它应该能工作——目前对我来说已经足够了。要么是 GoTK3 中发生了一些奇怪的事情,或者更可能的是,我在代码的某个地方犯了一些愚蠢的错误,这些错误我将来可能会(也可能不会)发现。重要的是我现在有一个变通方法(那三行代码)。

我问这个问题的原因是,我担心我遗漏了关于接收器和指针的某个要点,而这个要点我可能本应知道。

这是一个典型的Go语言方法值(method value)与nil接收器的问题。问题的关键在于方法值的求值时机。

在第一种写法中:

_, err = m.ToolbarRefresh.Connect("clicked", m.FundList.updateFundsValue)

m.FundList.updateFundsValue 是一个方法值(method value)。当创建方法值时,它会立即对接收器 m.FundList 进行求值。如果此时 m.FundListnil,那么创建的方法值就绑定了一个 nil 接收器。当这个函数被调用时,接收器 f 就是 nil,导致 panic。

在第二种写法中:

_, err = m.ToolbarRefresh.Connect("clicked", func() {
    m.FundList.updateFundsValue()
})

这里创建了一个匿名函数,在匿名函数内部调用 m.FundList.updateFundsValue()。这个调用是在匿名函数执行时才发生的,而不是在创建时。如果到执行时 m.FundList 已经被正确初始化,那么接收器就不会是 nil

要解决这个问题,你需要确保在创建方法值时接收器不是 nil。有几种方法:

  1. 延迟初始化:确保在调用 Connect 之前初始化 m.FundList
// 先初始化 FundList
m.FundList = &fundList{}

// 然后再连接信号
_, err = m.ToolbarRefresh.Connect("clicked", m.FundList.updateFundsValue)
  1. 使用闭包包装:就像你发现的第二种写法那样
_, err = m.ToolbarRefresh.Connect("clicked", func() {
    if m.FundList != nil {
        m.FundList.updateFundsValue()
    }
})
  1. 检查方法值创建时的接收器
if m.FundList != nil {
    _, err = m.ToolbarRefresh.Connect("clicked", m.FundList.updateFundsValue)
}

你提到的 m.shutDown 能正常工作的原因可能是:

  • m.shutDown 的接收器是 m 本身,而 m 在调用时已经初始化
  • 或者 shutDown 是一个值接收器方法,而不是指针接收器方法

示例代码展示问题本质:

type Handler struct {
    data string
}

func (h *Handler) Process() {
    if h == nil {
        println("接收器是nil")
        return
    }
    println("处理数据:", h.data)
}

func main() {
    var h *Handler
    
    // 方法值立即求值接收器
    f1 := h.Process  // 此时h是nil,但不会panic
    
    // 调用时接收器是nil
    f1()  // 输出: 接收器是nil
    
    // 闭包方式
    f2 := func() {
        h.Process()  // 调用时才求值
    }
    
    h = &Handler{data: "test"}
    f2()  // 输出: 处理数据: test
}

在你的 updateFundsValue 方法中,由于没有检查接收器是否为 nil 就直接使用了接收器的字段或方法,所以导致了 panic。建议在方法开始时添加 nil 检查:

func (f *fundList) updateFundsValue() {
    if f == nil {
        return
    }
    go func() {
        // 使用 f 的代码
    }()
}
回到顶部