函数也可以当作值来使用,一个简单的例子:
- func square(n int) int { return n * n }
-
- func main() {
- f := square
- fmt.Println(f(3)) // "9"
- }
-
函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。一个将函数值传递给函数的例子:
- func TryTimes(ctx context.Context, tryTime int, duration time.Duration, dofunc func() error) (err error) {
- if tryTime < 1 && tryTime > 5 {
- return fmt.Errorf("Error TryTime")
- }
- for i := 0; i < tryTime; i++ {
- err = dofunc()
- logs.CtxInfo(ctx, "try No.%v time err: %v", i, err)
- if err == nil {
- break
- }
- time.Sleep(duration)
- }
- return err
- }
-
通过func关键字后面不带函数名的方式,我们可以定义匿名函数,比如用在上面定义的TryTimes函数中:
- err = TryTimes(ctx, 3, 0, func() error {
- err := rpc.Call(...)
- return err
- })
-
后面的defer语句中,也可以用到匿名函数,将匿名函数放在defer的后面。
匿名函数中如果捕获了外部变量,称为闭包(closure),闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。
有两种匿名函数的写法需要注意一下:
- var i int
-
- defer fmt.Println(i) // 即刻确定i
-
- defer func() {
- fmt.Println(i) // 运行结束捕获i
- }
-
- i += 1
-
再看一个例子加深理解:
- func Increase() func() int {
- n := 0
- return func() int { // 这里返回了一个闭包函数,该函数访问了外部变量n
- n++
- return n
- }
- }
-
- func main() {
- in := Increase()
- fmt.Println(in()) // 1
- fmt.Println(in()) // 2
- }
-
在声明函数时,通过在参数类型前面加上...可以让函数接收任意数量的该类型参数,在golang的fmt.Sprintf,append,gorm的db.Where()中都使用了这种方式。一个例子:
- func sum(vals ...int) int {
- total := 0
- for _, val := range vals {
- total += val
- }
- return total
- }
-
defer中的内容可以在函数正常结束返回(return)或者函数产生panic异常结束的时候得到执行,这一机制可以让我们方便地进行一些资源的释放,或者捕获panic异常,因为不管在函数执行过程中发生了什么,defer中的内容总是确保可以得到执行。
在没有defer的情况下,我们需要小心地处理每一次错误返回以及异常处理,确保在函数返回时能够同时将开启的资源释放掉,这就意味着我们要在很多地方写上释放资源的语句。随着函数越来越复杂,维护清理逻辑将变得越来越困难。而使用defer之后,我们只需要在开启资源的时候同时加上一条defer语句回收资源,就能保证资源得到释放。
defer常用的场景举例:
- // 加锁之后释放锁
- var mu sync.Mutex
- mu.Lock()
- defer mu.Unlock()
-
- // 关闭数据库链接
- rows := *sql.Rows
- defer rows.Close()
-
- // 释放文件资源
- f, _ := os.Open(filename)
- defer f.Close()
-
当然,defer 还有一个常见用法是用来捕获运行时发生的panic异常,见下文。
在函数中可以多次使用 defer 语句,最终这些defer语句的执行顺序按照先入后出(FILO)的原则,即先声明的后执行。
defer 的执行时机是在 return 之前,看如下两个例子:
- func testDeferReturn() int {
- a := 1
- defer func() {
- a = 2
- }()
- return a // 返回 1
- }
-
- func testDeferReturn1() (ret int) {
- ret = 1
- defer func() {
- ret = 2
- }()
- return ret // 返回 2
- }
-
函数运行过程中有可能会发生 panic,比如数组的索引越界,或者尝试访问一个未初始化的值为 nil 的 map,或者尝试访问某个值为nil的结构体中的一些元素。如果这时不想让进程崩溃,可以使用 recover 捕获异常:
- func DoSomething() (err error) {
- defer func() {
- if p := recover(); p != nil {
- err = fmt.Errorf("internal error: %v", p)
- }
- }()
- /*
- ...
- */
- }
-
通过 runtime.Stack,我们可以获取完整的堆栈调用信息,帮助我们更好地定位问题:
- import "runtime/debug"
-
- func DoSomething() (err error) {
- defer func() {
- if p := recover(); p != nil {
- err = fmt.Errorf("internal error: %v", p)
- logs.Printf("fatal panic, error: %v, stack: %v", p, debug.Stack())
- }
- }()
- /*
- ...
- */
- }