init函数重置包级变量值

init函数重置包级变量值

init函数重置包级变量值 #

init 函数就好比 Go 包真正投入使用之前唯一的“质检员”,负责对包内部以及暴露到外部的包级数据(主要是包级变量)的初始状态进行检查。在 Go 标准库中,我们能发现很多 init 函数被用于检查包级变量的初始状态的例子,标准库 flag 包对 init 函数的使用就是其中的一个,这里我们简单来分析一下。

flag 包定义了一个导出的包级变量 CommandLine,如果用户没有通过 flag.NewFlagSet 创建新的代表命令行标志集合的实例,那么 CommandLine 就会作为 flag 包各种导出函数背后,默认的代表命令行标志集合的实例。

而在 flag 包初始化的时候,由于 init 函数初始化次序在包级变量之后,因此包级变量 CommandLine 会在 init 函数之前被初始化了,你可以看一下下面的代码:

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
    f := &FlagSet{
        name:          name,
        errorHandling: errorHandling,
    }
    f.Usage = f.defaultUsage
    return f
}
func (f *FlagSet) defaultUsage() {
    if f.name == "" {
        fmt.Fprintf(f.Output(), "Usage:\n")
    } else {
        fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)
    }
    f.PrintDefaults()
}

我们可以看到,在通过 NewFlagSet 创建 CommandLine 变量绑定的 FlagSet 类型实例时,CommandLine 的 Usage 字段被赋值为 defaultUsage。

也就是说,如果保持现状,那么使用 flag 包默认 CommandLine 的用户就无法自定义 usage 的输出了。于是,flag 包在 init 函数中重置了 CommandLine 的 Usage 字段:

func init() {
    CommandLine.Usage = commandLineUsage // 重置CommandLine的Usage字段
}
func commandLineUsage() {
    Usage()
}
var Usage = func() {
    fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])
    PrintDefaults()
}

这个时候我们会发现,CommandLine 的 Usage 字段,设置为了一个 flag 包内的未导出函数 commandLineUsage,后者则直接使用了 flag 包的另外一个导出包变量 Usage。这样,就可以通过 init 函数,将 CommandLine 与包变量 Usage 关联在一起了。

然后,当用户将自定义的 usage 赋值给了 flag.Usage 后,就相当于改变了默认代表命令行标志集合的 CommandLine 变量的 Usage。这样当 flag 包完成初始化后,CommandLine 变量便处于一个合理可用的状态了。

Viewpoint #

From #