Home >Backend Development >Golang >Let's talk about the zero value in Go language. What is its use?
Hello, everyone, I am asong
. Today I will talk to you about the zero value in the Go language. I was a C
language enthusiast when I was in college. After working, I felt that the Go
language was very similar to the C
language, so I chose the Go
language. In my work, I will compare some features of the two languages from time to time. Today I want to compare the zero-value feature. Friends who are familiar with the C
language know that local variables are not initialized by default in the C
language. Uninitialized variables can contain any value, and their use will lead to undefined behavior; if we do not initialize local variables, warning C4700 will be reported at compile time. This warning indicates a Bug
, this Bug
may cause unpredictable results or malfunctions in the program.There will be no such problem in the Go language. The designers of the Go language have learned some experience in designing the C
language, so the zero value specification of the Go
language is as follows:
The following content is from the official blog: https://golang.org/ref/spec#The_zero_value
When allocating storage space for a variable through declaration or new call, or through compound literal or When a make call creates a new value, and no explicit initialization is provided, the variable or value is given a default value. Each element of this type of variable or value is set to a zero value for its type: false for boolean type, 0 for numeric type, "" for string type, pointer, function, Interfaces, slices, channels and maps are nil. This initialization is done recursively, i.e. if no value is specified, the fields of each element of the structure array are cleared to zero.
For example, these two simple declarations are equivalent:
var i int var i int = 0
or the declaration of this structure:
type T struct { i int; f float64; next *T } t := new(T)
This structuret The zero value of the member field in
is as follows:
t.i == 0 t.f == 0.0 t.next == nil
Go
This feature in the language that always sets the value to a known default value plays a great role in the safety and correctness of the program. Important role, this also makes the entire Go
program simpler and more compact.
我们在看一些Go
语言库的时候,都会看到在初始化对象时采用"动态初始化"的模式,其实就是在创建对象时判断如果是零值就使用默认值,比如我们在分析hystrix-go
这个库时,在配置Command
时就是使用的这种方式:
func ConfigureCommand(name string, config CommandConfig) { settingsMutex.Lock() defer settingsMutex.Unlock() timeout := DefaultTimeout if config.Timeout != 0 { timeout = config.Timeout } max := DefaultMaxConcurrent if config.MaxConcurrentRequests != 0 { max = config.MaxConcurrentRequests } volume := DefaultVolumeThreshold if config.RequestVolumeThreshold != 0 { volume = config.RequestVolumeThreshold } sleep := DefaultSleepWindow if config.SleepWindow != 0 { sleep = config.SleepWindow } errorPercent := DefaultErrorPercentThreshold if config.ErrorPercentThreshold != 0 { errorPercent = config.ErrorPercentThreshold } circuitSettings[name] = &Settings{ Timeout: time.Duration(timeout) * time.Millisecond, MaxConcurrentRequests: max, RequestVolumeThreshold: uint64(volume), SleepWindow: time.Duration(sleep) * time.Millisecond, ErrorPercentThreshold: errorPercent, } }
通过零值判断进行默认值赋值,增强了Go
程序的健壮性。
为什么叫开箱即用呢?因为Go
语言的零值让程序变得更简单了,有些场景我们不需要显示初始化就可以直接用,举几个例子:
nil
,即使不用make
进行初始化也是可以直接使用的,例如:package main import ( "fmt" "strings" ) func main() { var s []string s = append(s, "asong") s = append(s, "真帅") fmt.Println(strings.Join(s, " ")) }
但是零值也并不是万能的,零值切片不能直接进行赋值操作:
var s []string s[0] = "asong真帅"
这样的程序就报错了。
利用零值可用的特性,我们配合空结构体的方法接受者特性,可以将方法组合起来,在业务代码中便于后续扩展和维护:
type T struct{} func (t *T) Run() { fmt.Println("we run") } func main() { var t T t.Run() }
我在一些开源项目中看到很多地方都这样使用了,这样的代码最结构化~。
我们经常使用sync
包中的mutex
、once
、waitgroup
都是无需显示初始化即可使用,拿mutex
包来举例说明,我们看到mutex
的结构如下:
type Mutex struct { state int32 sema uint32 }
这两个字段在未显示初始化时默认零值都是0
,所以我们就看到上锁代码就针对这个特性来写的:
func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled { race.Acquire(unsafe.Pointer(m)) } return } // Slow path (outlined so that the fast path can be inlined) m.lockSlow() }
原子操作交换时使用的old
值就是0
,这种设计让mutex
调用者无需考虑对mutex
的初始化则可以直接使用。
还有一些其他标准库也使用零值可用的特性,使用方法都一样,就不在举例了。
Go
语言零值的设计大大便利了开发者,但是零值并不是万能的,有些场景下零值是不可以直接使用的:
var s []string s[0] = "asong" var m map[string]bool m["asong"] = true
这两种写法都是错误的使用。
零值的指针就是指向nil
的指针,无法直接进行运算,因为是没有无内容的地址:
var p *uint32 *p++ // panic: panic: runtime error: invalid memory address or nil pointer dereference
这样才可以:
func main() { var p *uint64 a := uint64(0) p = &a *p++ fmt.Println(*p) // 1 }
error内置接口类型是表示错误条件的常规接口,nil值表示没有错误,所以调用Error
方法时类型error
不能是零值,否则会引发panic
:
func main() { rs := res() fmt.Println(rs.Error()) } func res() error { return nil } panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6f27]
在日常开发中我们会使用到闭包,但是这其中隐藏一个问题,如果我们函数忘记初始化了,那么就会引发panic
:
var f func(a,b,c int) func main(){ f(1,2,3) // panic: runtime error: invalid memory address or nil pointer dereference }
我们都知道channels
的默认值是nil
,给定一个nil channel c
:
<-c
Receiving from c
will block forever c <- v
Sending a value to c
will block forever close(c)
Close c
and cause panic
Let’s first introduce these scenarios where zero values are unavailable. Only by mastering these can we reduce the frequency of writing bug
in daily development.
Summarize several knowledge points described in this article:
Go
All variables or values in the language have default values, which plays a very important role in the safety and correctness of the programSome standard libraries in the Go
language use the zero value feature to simplify operations. The above is the detailed content of Let's talk about the zero value in Go language. What is its use?. For more information, please follow other related articles on the PHP Chinese website!