Defer, Panic, and Recover

4 August 2010

Go has the usual mechanisms for control flow: if, for, switch, goto. It also has the go statement to run code in a separate goroutine. Here I'd like to discuss some of the less common ones: defer, panic, and recover.

defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

For example, let's look at a function that opens two files and copies the contents of one file to the other:

func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
} dst, err := os.Create(dstName)
if err != nil {
return
} written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}

This works, but there is a bug. If the call to os.Create fails, the function will return without closing the source file. This can be easily remedied by putting a call to src.Close before the second return statement, but if the function were more complex the problem might not be so easily noticed and resolved. By introducing defer statements we can ensure that the files are always closed:

func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close() dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close() return io.Copy(dst, src)
}

Defer statements allow us to think about closing each file right after opening it, guaranteeing that, regardless of the number of return statements in the function, the files will be closed.

The behavior of defer statements is straightforward and predictable. There are three simple rules:

1. A deferred function's arguments are evaluated when the defer statement is evaluated.

In this example, the expression "i" is evaluated when the Println call is deferred. The deferred call will print "0" after the function returns.

func a() {
i := 0
defer fmt.Println(i)
i++
return
}

2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.

This function prints "3210":

func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}

3. Deferred functions may read and assign to the returning function's named return values.

In this example, a deferred function increments the return value i after the surrounding function returns. Thus, this function returns 2:

func c() (i int) {
defer func() { i++ }()
return 1
}

This is convenient for modifying the error return value of a function; we will see an example of this shortly.

Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.

Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

Here's an example program that demonstrates the mechanics of panic and defer:

package main

import "fmt"

func main() {
f()
fmt.Println("Returned normally from f.")
} func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
} func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}

The function g takes the int i, and panics if i is greater than 3, or else it calls itself with the argument i+1. The function f defers a function that calls recover and prints the recovered value (if it is non-nil). Try to picture what the output of this program might be before reading on.

The program will output:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

If we remove the deferred function from f the panic is not recovered and reaches the top of the goroutine's call stack, terminating the program. This modified program will output:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4 panic PC=0x2a9cd8
[stack trace omitted]

For a real-world example of panic and recover, see the json package from the Go standard library. It decodes JSON-encoded data with a set of recursive functions. When malformed JSON is encountered, the parser calls panic to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'unmarshal' methods of the decodeState type in decode.go).

The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

Other uses of defer (beyond the file.Close example given earlier) include releasing a mutex:

mu.Lock()
defer mu.Unlock()

printing a footer:

printHeader()
defer printFooter()

and more.

In summary, the defer statement (with or without panic and recover) provides an unusual and powerful mechanism for control flow. It can be used to model a number of features implemented by special-purpose structures in other programming languages. Try it out.

By Andrew Gerrand

Related articles

15 Defer, Panic, and Recover的更多相关文章

  1. GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover

    Simple error handling primitives:        https://github.com/pkg/errors Defer, Panic, and Recover:    ...

  2. Golang 入门系列(十四)defer, panic和recover用法

    以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...

  3. 6.Go-错误,defer,panic和recover

    6.1.错误 Go语言中使用builtin包下error接口作为错误类型 Go语言中错误都作为方法/函数的返回值 自定义错误类型 //Learn_Go/main.go package main imp ...

  4. go语言defer panic recover用法总结

    defer defer是go提供的一种资源处理的方式.defer的用法遵循3个原则 在defer表达式被运算的同时,defer函数的参数也会被运算.如下defer的表达式println运算的同时,其入 ...

  5. panic和recover的使用规则

    转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我们温习一下panic和recover的使用规则. 在golang当中不存在tye ... catch 异常处理 ...

  6. Go语言异常处理defer\panic\recover

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.因为开发者很容易滥用异常, ...

  7. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  8. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  9. Go基础系列:defer、panic和recover

    defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...

随机推荐

  1. 【Cf #178 A】Shaass and Lights(组合数)

    考虑两个灯之间的暗灯,能从左边或右边点亮两种顺序,而最左端或最右端只有一种点亮顺序. 先不考虑点灯顺序,总共有n - m个灯要点亮,对于连续的一段暗灯,他们在总的点灯顺序中的是等价的,于是问题就可以抽 ...

  2. linux内核分析 第七周 Linux内核如何装载和启动一个可执行程序

    一.编译链接的过程和ELF可执行文件格式 vi hello.c gcc -E -o hello.cpp hello.c -m32 //预处理.c文件,预处理包括把include的文件包含进来以及宏替换 ...

  3. Linux编程中 #define _XOPEN_SOURCE的作用

    [误解]#define _XOPEN_SOURCE决不是简单的宏定义它是使程序符合系统环境的不可缺少的部分 [概念]Glibc 所实现全部或部分规范下的功能有:1.ISO C: C语言国际标准. 2. ...

  4. #define后面只带有一个标识符

    经常看到有#define后只有一个标识符的语句,这样是做宏开关用 宏定义编译前会被编译器进行替换,只有一个标识符的情况,如果在代码里使用了这个标识符,会被替换为空,也就是相当于没加. 用来做编译开关的 ...

  5. maven管理工具

    Maven解决的问题: 1. 使用maven前搭建项目需要引入各种jar包,并且还可能有jar包冲突的问题 解决jar包冲突的方式: 1. 第一声明优先原则 2. 路径近者优先原则. 直接依赖路径比传 ...

  6. Android Studio 换主题(Material Theme..)

    1.去如下网址下载自己喜欢的主题文件xx.jar http://color-themes.com/?view=index 2. 导入方式 下载主题—xxx.jar 注意:如果我们下载下来的jar名字如 ...

  7. 【cf859E】Desk Disorder

    Portal --> cf859E Solution ​​ 我们可以将每一个人看成一条边,将位置看成点,然后一个人在新的方案中可以选择的位置就是这条边连接的两个点,然后我们就得到了一个图 ​ 注 ...

  8. 850. 矩形面积 II

    我们给出了一个(轴对齐的)矩形列表 rectangles . 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标,(x2,y2)是该矩形右 ...

  9. E. Turn Off The TV Educational Codeforces Round 29

    http://codeforces.com/contest/863/problem/E 注意细节 #include <cstdio> #include <cstdlib> #i ...

  10. PDF文档小技巧整理一览

    1.福昕阅读器文档背景修改为保护眼睛的颜色? 1)文件 -> 偏好设置 -> 访问 -> 勾选 "改变文档颜色" 2)选择 '自定义颜色'->'页面背景颜色 ...