Golang Traps

PPG007 ... 2021-12-26 About 3 min

# Golang Traps

  • 可变参数是空接口类型时,传入空接口的切片要注意参数展开问题:

    var a = []interface{}{1, 2, 3}
    fmt.Println(a)
    fmt.Println(a...)
    
    1
    2
    3
  • 数组是值传递,如果数组做函数参数那么在函数中的修改将不起作用,可以使用切片。

  • map 的遍历顺序是不固定的,每次遍历顺序可能都不一样。

  • recover 必须在 defer 语句中,且这个语句是一个函数的执行。

  • 并发时注意主线程退出导致其他线程结束。

  • 不同 goroutine 之间不满足顺序一致性内存模型。

  • 闭包错误地引用同一个变量。

    func main() {
      for i := 0; i < 5; i++ {
        defer func ()  {
          fmt.Println(i)// 全部是 5,可以通过每轮循环生成一个局部变量或者通过函数参数传入解决。
        }()
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
  • defer 语句在函数退出时才会执行,如果在循环体中使用 defer 语句将会在循环结束后执行,如果希望每次循环后执行可以去掉 defer 并将语句放在循环体最后或者将 defer 后语句封装为一个函数。

# 空接口和 nil 的区别

声明一个 interface{} 类型的变量但不进行初始化,这时这个变量值为 nil,本身也是 nil,如果将一个结构体的 nil 指针变量赋值给这个空接口变量,则这个空接口变量的值是 nil,但是本身不是 nil,所以如果打印这个变量会输出 nil,但是如果对其进行 if != nil 会返回 false。

一个 interface{} 变量包含了两个指针,一个指针指向值在编译时确定的类型,一个指针指向实际的值,所以值为 nil 不代表这个变量是 nil。

package main

import (
	"log"
)

type DemoInterface interface{
	Demo() string
}

type DemoImpl struct {
	message string
}

func (d DemoImpl) Demo() string {
	return d.message
}

var di *DemoImpl
var d DemoInterface

func demoImplFunc() *DemoImpl {
	return di
}

func main() {
	d = demoImplFunc()
	if d != nil {
		log.Println(d.Demo())
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

在上面的代码中,d = di 这行代码是将一个接口的为 nil 的实现赋值给这个接口的一个变量,此时 d 就是一个空接口,值为 nil 但是类型指针指向实现结构体所以 if d != nil 会返回 true,但是一旦调用接口的方法会由于值为 nil 出现空指针 panic。

但是如果是 d := demoImplFunc(),相当于新定义了一个临时变量,类型就是实现类,因此 if d != nil 会返回 false,也就不会 panic。

Note

只要涉及到实现和接口的转换就存在这个问题。

解决方法

  • 在发生接口和实现转换时进行判空操作。例如上面的例子中可以使用一个临时变量接收函数的返回值,再判断这个返回值是否为空,再决定是否转换。
  • 使用具体的实现变量从而杜绝实现和接口的转换。但是这么做会破坏代码的抽象性。
Last update: March 8, 2022 09:15
Contributors: Koston Zhuang , PPG007