对等于nil
的函数defer
操作
如果通过defer
调用一个值等于nil
的函数,会引发panic
错误。1
2
3
4
5func main() {
var run func() = nil
defer run()
fmt.Println("runs")
}
输出:1
2
3runs
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
2query 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
3db := &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: