对等于nil的函数defer操作
如果通过defer调用一个值等于nil的函数,会引发panic错误。1
2
3
4
5func main() {
	var run func() = nil
	defer run()
	fmt.Println("runs")
}
输出:1
2
3
4runs
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x452dc8]
原因:
在函数进行到最后之后,才执行run()函数,并引发panic错误,因为这是一个值为nil的函数
在for循环中调用defer

上面的defer row.Close()在for循环中并不会立即执行直到函数结束(第一次循环结束并不会执行)。在调用defer会产生一个函数调用栈,如果循环次数过多将会产生意料之外的问题。
解决方法:
1.直接调用row.Close()不使用defer
| 1 | func () { | 
2.将操作放入一个匿名函数中,当函数结束时将会执行defer调用的函数
| 1 | func () { | 
Defer as wrapper
有些时个你需要对闭包使用defer以追求实用或者有其它的一些原因。比如:要打开数据库连接,然后运行一些查询,最后运行以确保断开连接。1
2
3
4
5
6
7
8
9
10
11
12
13type database struct{}
func (db *database) connect() (disconnect func()) {
	fmt.Println("connect")
	return func() {
		fmt.Println("disconnect")
	}
}
func main() {
	db := &database{}
	defer db.connect()
	fmt.Println("query db...")
}
输出:1
2
3query db...
connect
为什么最后没有执行disconnect?这里出现这个bug的原因是connect函数返回的值并没有执行而是被保存了起来。
解决方法:1
2
3
4
5
6
7func main() {
	db := &database{}
	close := db.connect()
	defer close()
	fmt.Println("query db...")
}
不好的实践:
虽然下面的代码可以正常的执行,但是不推荐这样使用。1
2
3
4db := &database{}
defer db.connect()()
..
Defer in a block
你可能会想deferred func会在结束一个代码块的时候执行,实际上deferred func只会在包含它的函数结束的时候执行。1
2
3
4
5
6
7
8
9func main() {
	{
		defer func() {
			fmt.Println("block: defer runs")
		}()
		fmt.Println("block: ends")
	}
	fmt.Println("main: ends")
}
输出:1
2
3block: ends
main: ends
block: defer runs
原因:deferred func只会在函数代码块结束的时候执行。
解决方法:使用匿名函数1
2
3
4
5
6
7
8
9
10
11
12
13func main() {
  func() {
    defer func() {
      fmt.Println("func: defer runs")
    }()
    fmt.Println("func: ends")
  }()
  fmt.Println("main: ends")
}
// output:
func: ends
func: defer runs
main: ends
Deferred method陷阱
不使用指针:1
2
3
4
5
6
7
8
9
10
11type Car struct {
  model string
}
func (c Car) PrintModel() {
  fmt.Println(c.model)
}
func main() {
  c := Car{model: "DeLorean DMC-12"}
  defer c.PrintModel()
  c.model = "Chevrolet Impala"
}
输出:1
DeLorean DMC-12
使用指针:1
2
3func (c *Car) PrintModel() {
  fmt.Println(c.model)
}
输出:1
Chevrolet Impala
当使用defer时,传递给函数的参数会被立即保存下来而不用等到函数执行。
当一个方法的接收者是值接收者时,这个接收者会被拷贝(当调用defer函数的时候)所以当修改Car结构的数据被修改后,defer调用的函数并不会知道,因为它使用的是拷贝过来的数据。
如果一个方法的接收者是指针,当调用defer时,虽然也会产生一个新的指针,但这和原因的指针指向的是同一个对象,所以任何的对于结构体Car中的任何改变都能被探测到。
打印Z —> A
| 1 | func main() { | 
执行for循环的时候进行压栈操作,当函数结束的时候弹出然后执行压栈的函数
参数作用域问题,参数被覆盖
| 1 | type reader struct {} | 
也许的你期待的返回是“close Error“,但实际返回的err却是nil。
原因:在if代码块中使用新的err覆盖了name result中的err值,所以release()返回了原来的result-value。
这里还有疑惑,既然被被覆盖了难道不应该返回不为nil的值吗
解决方案:使用=而不:=1
2
3
4
5
6
7
8func release(r io.Closer) (err error)  {
	defer func() {
		if err = r.Close();err != nil{
			fmt.Println("err in r.close")
		}
	}()
	return
}
即时计算参数的值
传递给deferred func的参数是在函数注册(调用defer)的时候进行计算的而不是当它执行的时候计算。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18type message struct {
	content string
}
func (p *message) set(c string) {
	p.content = c
}
func (p *message) print() string {
	return p.content
}
func main() {
	m := &message{content: "Hello"}
	defer fmt.Print(m.print())
	m.set("World")
	// deferred func runs
}
//output: Hello
为什么输出不是”World”
在调用defer时,fmt.Print是在函数结束之前执行,但是传递给它的参数m.print()会立即执行,所以传递给fmt.Print的参数是”Hello”,而且这个值会被保存直到defer中的函数执行。
for循环中的捕获
在循环中deferred func将会看到最新的值当函数执行的时候,有一种情况除外,就是把值当作参数传递给了deferred func。
| 1 | func main() { | 
Why?
当defer中的函数运行时,deferred func看到的是i的最新值。因为当调用defer进行函数注册时,Go运行时捕获的是变量i的地址。当循环结束后i的值变为3,所以当运行defer中的函数时因为指针指向的是同一个值所以输出都为3。
解决方案1:将值当作参数传递给defer中的函数1
2
3
4
5
6
7
8
9
10
11func main() {
	for i := 0; i < 3; i++ {
		defer func(i int) {
			fmt.Println(i)
		}(i)
	}
}
//output:
2
1
0
解决方案2:使用一个新的变量i覆盖掉外一层的i1
2
3
4
5
6
7
8func main() {
	for i := 0; i < 3; i++ {
		i := i
		defer func() {
			fmt.Println(i)
		}()
	}
}
解决方案3:如果只有一个函数调用1
2
3
4
5func main() {
	for i := 0; i < 3; i++ {
		defer fmt.Println(i)
	}
}
defer中函数的返回值
defer函数中的返回值对于调用者是不可见,但你仍可以使用命名返回值的方法改变结果值。1
2
3
4
5
6
7
8
9
10
11func release() error {
	defer func() error {
		return errors.New("error")
	}()
	return nil
}
func main() {
	r := release()
	fmt.Println(r)
}
//outpu:<nil>
解决方案:改变命名结果值1
2
3
4
5
6func release() (err error) {
	defer func() {
		err = errors.New("error")
	}()
	return nil
}
在deferred func之后调用recover函数
| 1 | func main() { | 
在defer之外使用recover()不能捕获panic。
解决方案:1
2
3
4
5
6
7
8
9
10
11
12
13func do() {
	defer func() {
		r := recover()
		fmt.Println("recovered:", r)
	}()
	panic("error")
}
func main() {
	do()
}
//output
recovered: error
错误的顺序调用defer func
| 1 | func do() error { | 
原因是我们没有检查http请求是否成功,如果失败则会出现如上错误。因为出错的情况下res为nil,当执行res.Body的时候则会引发panic。
解决方案:加个判断1
2
3
4
5
6
7
8
9
10
11func do() error {
  res, err := http.Get("http://notexists")
  if res != nil {
    defer res.Body.Close()
  }
  if err != nil {
    return err
  }
  // ..code...
  return nil
}
不检查错误
不要以后把善后的工作委派给了defer就可以安全的释放资源。你有可能会丢失一些有用的报错信息。
不推荐:
f.Close()可能会报错,但是我们意识不到。1
2
3
4
5
6
7
8
9func do() error {
  f, err := os.Open("book.txt")
  if err != nil {
    return err
  }
  defer f.Close()
  // ..code...
  return nil
}
更好的做法是检查错误并处理错误1
2
3
4
5
6
7
8
9
10
11
12
13func do() error {
  f, err := os.Open("book.txt")
  if err != nil {
    return err
  }
  defer func() {
    if err := f.Close(); err != nil {
      // log etc
    }
  }()
  // ..code...
  return nil
}
你还可以使用命名结果值返回defer中的错误020年10月19日1
2
3
4
5
6
7
8
9
10
11
12
13func do() (err error) {
  f, err := os.Open("book.txt")
  if err != nil {
    return err
  }
  defer func() {
    if ferr := f.Close(); ferr != nil {
      err = ferr
    }
  }()
  // ..code...
  return nil
}
Note:
你可以使用这个包包裹多种错误。这是有必要的,因为f.Close可能会覆盖在其之前的错误。在一个错误中包裹另一个错误并打印到日志中可以更好的排查错误。
你也可以使用这个包来捕获你不想检查的错误
释放相同的资源
你可能会对一个资源进行多次释放操作,这会发生预料之后的问题。
| 1 | func do() error { | 
WHY?
因为当deferred func运行时,看到的是最新的值,所以看到的变量f是最新的那个(another-book.txt)。因为同一资源被释放了两次。
解决方案: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
26func do() error {
  f, err := os.Open("book.txt")
  if err != nil {
    return err
  }
  defer func(f io.Closer) {
    if err := f.Close(); err != nil {
      // log etc
    }
  }(f)
  // ..code...
  f, err = os.Open("another-book.txt")
  if err != nil {
    return err
  }
  defer func(f io.Closer) {
    if err := f.Close(); err != nil {
      // log etc
    }
  }(f)
  return nil
}
//output:
closing resource #another-book.txt
closing resource #book.txt
panic/recover可以获取可返回任何类型
字符串:1
2
3
4
5
6
7
8
9
10func errorly() {
  defer func() {
    fmt.Println(recover())
  }()
  if badHappened {
    panic("error run run")
  }
}
//output:
"error run run"
Error:1
2
3
4
5
6
7
8
9
10func errorly() {
  defer func() {
    fmt.Println(recover())
  }()
  if badHappened {
    panic(errors.New("error run run")
  }
}
//output:
"error run run"
可接收任何类型的参数
panic不仅可以接收字符串也可以接收error类型。这意味着你可以把任何类型的参数传递给panic然后从recover中获取。
| 1 | type myerror struct {} | 
WHY?
在GO中interface{}类型意味着任何类型.
panic和recover的定义1
2func panic(v interface{})
func recover() interface{}
它们的工作流程如下
panic(value) —> recover() —> value
recover只是返回传递给panic的值
REF: