Go语言的comma ok语法

最近在组内review代码时经常看到一些比较别扭的写法,感觉用Go语言中的comma ok语法能够很好的解决。这里结合3个场景对其用法进行一下简单总结:

清晰的map访问

首先看一下不使用comma ok的一般写法:

1
2
m := make(map[string]int)
v := m["k"]

按照以上写法,当m中不存在k这个key时,v的值是自身类型对应的“零值”。如int类型的零值为0,string类型的零值为"",slice、map、func、channel和指针类型的零值为nil。

假如以上示例中v的值为0,我们无法区分是因为以下两种情况中的哪一种造成的:

  1. m中存在k,并且对应的值就是0;
  2. m中根本不存在k,v实际上被赋值为“零值”。

针对以上情况,有的同学将map的value类型改成了指针,通过判断是否为nil来区分。这样会造成额外的内存分配,而且实现方式也不优雅。

再来看一下使用comma ok语法能有什么不同:

1
v, ok := m["k"]

无论以上示例中v的值是否为0,ok的真假状态直接对应key是否存在:

  1. 如果ok为false,表示m中不存在k;
  2. 如果ok为true,表示m中存在k。

安全的类型断言

首先来看一下不使用comma ok的类型断言:

1
2
3
i := 10
var a interface{} = i
s := a.(string)

以上实例中a的实际类型为int,无法通过类型断言转换为string,所以会因断言失败而panic。

再看一下comma ok语法的方式:

1
s, ok := a.(string)

以上示例中:

  1. 断言失败时不会panic,ok为false,s为string类型的“零值”,即""
  2. 断言成功时,ok为true,s被赋值为a底层的string。

channel receive

Go语言的channel被设计为能够用作“事件通知”,具体实现就是在close之后可以无限receive,不会阻塞并且得到的值都是对应元素类型的“零值”。

首先看一下普通的channel receive语法:

1
2
ch := make(chan int)
i := <-ch

如果以上示例中i等于0,我们无法判断:

  1. 通道ch中确实传递过来一个0;
  2. 因为ch已经被关闭,所以我们得到一个“零值”。

再来看一下comma ok语法:

1
i, ok := <-ch

如果因为ch关闭而得到一个“零值”,ok会是false。

对于类似如下的有缓冲channel,如果在其被关闭后缓冲中还有数据,此时comma ok得到的ok会是true,直到缓冲数据读尽变成false:

1
ch := make(chan int, 10)