Go 零值和空值的判断问题

2023年 8月 22日 34.4k 0

大家好,我是煎鱼。

前段时间分享了《Go 将增加内置的零值标识符 zero!》的新预定义标识符 zero。对应的签名如下:

// zero is a predeclared identifier representing the zero value
// for array and struct types.
var zero Type

我原想着还是一个有一点点新改变。不过综合大家意见来看,由于只是针对数组(array)和结构体(struct),许多同学表示这个是比较鸡肋的。因为仍然无法很好的解决 Go 零值和空值的识别问题,大失所望。

本文是对零值和空值判断现状进行梳理和分享。

快速复习零值

基本类型

var a int
var b bool
var c string

func main() {
	fmt.Printf("%+v\n", a) // 0
	fmt.Printf("%+v\n", b) // false
	fmt.Printf("%+v\n", c) // ""
}

复合类型

var a []int
var b map[string]int
var c [7]int
var d *int
var g chan int
var p Person

type Person struct {
	Name string
	Age  int
}

func main() {
	fmt.Printf("%+v\n", a) // []
	fmt.Printf("%+v\n", b) // map[]
	fmt.Printf("%+v\n", c) // [0 0 0 0 0 0 0]
	fmt.Printf("%+v\n", d) // 
	fmt.Printf("%+v\n", g) // 
	fmt.Printf("%+v\n", p) // {Name: Age:0}
}

进行空值判断

在实际的 Go 业务应用中,我们需要对数据的零值和空值进行区分,以便于实现一些空值的业务逻辑处理。

常见的有两种做法。如下:

  • 在变量声明时,使用指针来处理,将其声明为指针类型。
  • 在定义变量缺省值时,错开类型的零值。例如:int 零值是 0,业务里字段缺省值定义为 1 和 2 等。
  • 第一种是用的最多的,也是前文评论区大家有所提到的。对于基础类型,具体的代码示例如下:

    var a *int
    var b *bool
    var c *string
    
    func main() {
    	if a == nil {
    		fmt.Print("煎鱼")
    	}
    	if b == nil {
    		fmt.Print("进")
    	}
    	if c == nil {
    		fmt.Print("脑子了")
    	}
    }
    

    输出结果:煎鱼进脑子了。

    对于复合类型,也是一样的:

    var a []int
    var b map[string]int
    var g chan int
    
    var c *[7]int
    var d *int
    var p *Person
    

    对于复合类型的一些值类型,由于零值有可能是程序赋的值,也有可能是真空值。因此同样需要加上指针,用于识别。

    在 Go 业务程序上,大家为了解决这个零值和空值的判别问题。会采取类似的方式去编写包和程序。

    如下代码:

    type Person struct {
    	Name *string
    	Age  *int
    }
    
    func main() {
    	s := `{
    		"name": "煎鱼"
    	}`
    
    	var p Person
    	err := json.Unmarshal([]byte(s), &p)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Printf("p: %+v\n", p)
    	fmt.Println(*p.Name)
    }
    

    输出结果:

    p: {Name:0xc00010c380 Age:}
    煎鱼
    

    可以看到所传入的 json 字符串并不包含 age 字段,因此其值为 nil。

    如果是为空字符串:

    s := `{
    		"name": ""
    	}`
    

    解析后输出的结果为:

    p: {Name:0xc000096380 Age:}
    
    

    以此就可以实现空值和零值的有效区分,不再为此判别烦恼太多。

    但也引入了一个麻烦的点,就是在获取值时需要使用 *p.Name 的方式。如果希望 “屏蔽” 这个用法,一般还会再做一次函数封装作为 Getter 的方法。

    新增 zero 解决什么问题

    显然我再回去看即将新加入的 zero 标识符时,会发现他能够成功 Go 的机缘是对零值的比较判断,而并非空值的原因。

    zero 使用场景是:

    if val == zero(MyType) {}
    

    又或是:

    func example[T any]() T {
      // do something that returns an error...
      if err != nil {
        return zero(T)
      }
      //...
    }
    

    这么一梳理,发现确实和我们想象中的有一定的差距。因为我们在实际的零值和空值的判断中,更需要的是对内部字段的数值判断例如:结构体里的某些字段,比较少是只对结构体本身做空值判断。

    总结

    今天根据大家热议的反馈,重新梳理了 Go 中零值和空值的现状和判断技巧。感觉本次 zero 的加入,真的是只加强了结构体和数组类型本身的零值判断,而没有针对空值的好手段。

    综合来看,考虑到规范(SPEC)中零值是官方的规范约定,改变的可能性也很低了。个人感觉核心团队新增判别方式或优化的可能性比较低。

    大家平时除了使用指针和预定义非零值的枚举值外,还会用什么方法来判别零值和空值呢,也欢迎大家分享你的看法!

    文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

    Go 图书系列

    • Go 语言入门系列:初探 Go 项目实战
    • Go 语言编程之旅:深入用 Go 做项目
    • Go 语言设计哲学:了解 Go 的为什么和设计思考
    • Go 语言进阶之旅:进一步深入 Go 源码

    推荐阅读

    • 又有新功能!Go 将有生成新模板的 gonew 工具链
    • Go1.21 那些事:泛型库、for 语义变更、统一 log/slog、WASI 等新特性,你知道多少?
    • 互联网大厂裁员的原因和预兆- 互联网大厂裁员的原因和预兆

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论