Go 在结构体中定义下划线(_)字段原来还有这个特殊用途?

2024-06-24 20:14:57 浏览数 (2)

前言

Go 语言中,我们经常会看到下划线(_)的使用,例如将 _ 作为占位符,用于忽略不需要的变量,或者导入但不使用的包(即 Import for side-effects),以及忽略类型转换中的变量等等。然而,大多数人可能没有见过在结构体中使用下划线(_)的情况,例如定义一个名为 _ 的结构体字段。那么,定义这样一个字段的用途是什么呢?本文将为你揭示这个疑惑。

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

有无下划线(_)字段的结构体代码示例对比

首先,我们来看不包含下划线(_)字段的结构体代码示例。

model 包里定义一个 User 结构体,包含 NameAge 两个字段。

代码语言:go复制
type User struct {
    Name string
    Age  int
}

使用 位置初始化具名字段初始化 两种方式声明结构体变量。

代码语言:go复制
user := model.User{"陈明勇", 18}
user = model.User{Name: "陈明勇", Age: 18}

在上述代码中,无论是定义结构体,还是声明结构体,都没有任何问题。

我们再来看下包含下划线(_)字段的结构体代码例子:

model 包里定义一个 User 结构体,包含 NameAge 以及 _ 三个字段。

代码语言:go复制
type User struct {
    Name string
    Age  int
    _    struct{}
}

使用 位置初始化具名字段初始化 两种方式声明结构体变量。

代码语言:go复制
// 编译错误 too few values in struct literal of type model.User
user := model.User{"陈明勇", 18}
// 编译错误 implicit assignment to unexported field _ in struct literal of type model.User
user = model.User{"陈明勇", 18, struct{}{}}
// 正常
user = model.User{}
user = model.User{Name: "陈明勇", Age: 18}

在上述例子中,如果通过 user := model.User{"陈明勇", 18}model.User{"陈明勇", 18, struct{}{}} 这两种 位置初始化 的方式声明结构体变量,程序将会编译错误,而通过 零值初始化具名字段初始化 的方式去声明结构体变量则没有问题。

通过对比有无下划线(_)字段的结构体代码示例,我们可以总结出在结构体中定义下划线(_)字段的用途:在结构体中定义一个名为 _ 的字段,可以强制要求该结构体在初始化时必须使用具名字段初始化(声明零值结构体变量的场景除外)。

原理浅析

当我们使用 位置初始化 的方式声明结构体时,需要按照结构体字段的顺序依次提供所有字段的值。

如果在结构体中定义了一个名为 _ 的字段,那么使用 位置初始化 的方式时,如果没有提供 _ 字段的值,编译器会提示 too few values in struct literal of type XXX,这是因为没有提供所有的结构体字段值。

即使按照结构体字段的顺序提供了所有字段的值,编译器也会报错,提示 implicit assignment to unexported field _ in struct literal of type XXX。这是因为 _ 字段的首字母没有大写,被认为是未导出的字段,我们不能隐式赋值给未导出的字段,因此不能通过位置初始化进行赋值。

综上所述,由于无法通过 位置初始化 的方式去声明这个结构体的变量,我们只能通过 零值初始化具名字段初始化 的方式去声明结构体变量。

小结

通过本文的探讨,我们了解了在 Go 语言中结构体字段命名使用下划线(_)的特殊用途。

具体来说,定义一个名为 _ 的字段可以有效地强制开发者在初始化结构体时使用具名字段初始化,而不是位置初始化。这样做的好处包括:

  • 代码可读性:具名字段初始化使得代码更具可读性和可维护性,因为每个字段的值都显式地与字段名关联。
  • 避免错误:位置初始化需要严格遵循字段顺序,容易引入错误。具名字段初始化则避免了这一问题。

0 人点赞