Golang中如何处理引用循环错误

Golang中如何处理引用循环错误 大家好

我正在自学一点 text/template 包时,遇到了这个据说很常见的错误。我在 Go 的 Github issues 和这里都稍微搜索了一下,但没有找到答案。我知道存在引用循环,但这个问题不应该已经被处理了吗?这里是一小段代码。

package something

import (
         "bytes"
         "text/template"
)

type Entry func() string
var Registry []Entry = []Entry{Summary, A, B}

func Summary() string {
        buf := &bytes.Buffer{}
        str := "{{.}}"
        tlp := template.Must(template.New("Summ").Parse(str))
        tlp.Execute(buf, Registry)
        return buf.String()
}

func A() string {
        return "a"
}

func B() string {
        return "b"
}

最终会出现类似这样的错误:

# something
something.go:?:?: initialization loop:
        .../something.go:?:?: Registry refers to
        .../something.go:?:?: Summary refers to
        .../something.go:?:?: Registry

更多关于Golang中如何处理引用循环错误的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

太棒了!感谢提供问题链接!

更多关于Golang中如何处理引用循环错误的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我不确定 init 的预期用途是什么,但 rsc 在这里推荐了这种方法。

这很有趣。我可以复现这个问题。我解决这个问题的方法是将 Registry 的初始化放到一个 init 函数中,像这样:

func init() {
    Registry = []Entry{Summary, A, B}
}

然后问题似乎就解决了。

感谢您的回答,Sean。我完全忘记了 init()。很高兴知道这是一种解决方法,但查看 https://golang.org/doc/effective_go.html#init,我不确定其原始目的是为了避免那种技术细节。除了这句隐晦的说明 Besides initializations that cannot be expressed as declarations, 之外,init() 的目的似乎是以单例方式初始化内容。

这是一个典型的初始化循环(initialization loop)问题,不是引用循环(reference cycle)。问题在于 Registry 在初始化时直接引用了 Summary 函数,而 Summary 函数在执行时又试图使用 Registry,形成了初始化依赖循环。

问题分析:

  1. Registry 的初始化需要调用 Summary 函数(作为 Entry 类型值)
  2. Summary 函数执行时,template.Execute 尝试访问 Registry 变量
  3. 此时 Registry 尚未完全初始化,导致循环依赖

解决方案:Registry 的初始化与函数定义分离,使用 init() 函数或在运行时动态构建。

示例代码:

package something

import (
    "bytes"
    "text/template"
)

type Entry func() string

// 先声明变量,不初始化
var Registry []Entry

func init() {
    // 在init函数中初始化,此时所有函数都已定义
    Registry = []Entry{Summary, A, B}
}

func Summary() string {
    buf := &bytes.Buffer{}
    str := "{{.}}"
    tlp := template.Must(template.New("Summ").Parse(str))
    tlp.Execute(buf, Registry)
    return buf.String()
}

func A() string {
    return "a"
}

func B() string {
    return "b"
}

或者使用函数返回 Registry

package something

import (
    "bytes"
    "text/template"
)

type Entry func() string

func GetRegistry() []Entry {
    return []Entry{Summary, A, B}
}

func Summary() string {
    buf := &bytes.Buffer{}
    str := "{{.}}"
    tlp := template.Must(template.New("Summ").Parse(str))
    tlp.Execute(buf, GetRegistry())
    return buf.String()
}

func A() string {
    return "a"
}

func B() string {
    return "b"
}

关键点: Go 的初始化顺序是确定性的,包级变量的初始化按照声明顺序进行,遇到依赖未初始化变量时就会报初始化循环错误。通过 init() 函数或运行时函数调用可以打破这种循环依赖。

回到顶部