return/defer/panic

return的实现逻辑

  • 第一步给返回值赋值(若是有名返回值直接赋值,匿名返回值 则 先声明再 赋值) ;
  • 第二步调用RET返回指令并传入返回值,RET会检查是否存在defer语句,若存 在就先逆序插播 defer语句 ;
  • 最后 RET 携带返回值退出函数 。
  • return 不是一个原子操作,函数返回值与 RET 返回值并不一定一致

defer、 return、返回值三者顺序

  • return 最先给返回值赋值;
  • 接着 defer 开始执行一些收尾工作;
  • 最后 RET 指令携带返回值退出函数。

defer 内容

  • defer 后面必须是函数调用语句,defer a = 1 + 1 或者 defer a++ 都是错误的;
  • defer 后面的语句在函数执行结束的时候才会被调用,顺序是后入先出;
  • defer 后面的语句参数是实时解析的;

defer执行时修改变量

defer函数定义时,对外部变量的引用方式有两种,分别是函数参数以及作为闭包引用。

  • 在作为函数参数的时候,在defer定义时就把值传递给defer,并被缓存起来。(实时解析)
  • 如果是作为闭包引用,则会在defer真正调用的时候,根据整个上下文去确定当前的值。

闭包

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

return xxx这一条语句并不是一条原子指令
含有defer函数的外层函数,返回的过程是这样的:

  • 先给返回值赋值
  • 调用defer函数
  • 返回到更上一级调用函数中
返回值 = xxx

调用defer函数(这里可能会有修改返回值的操作)

return 返回值

示例

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

可改写为:
func f() (result int) {
    result = 0
    //在return之前,执行defer函数
    func() {
        result++
    }()
    return
}

返回值:1
func f() (r int) {
    t := 5
    defer func() {
        t = t + 5
    }()
    return t
}

可改写为:
func f() (r int) {
    t := 5
    //赋值
    r = t
    //在return之前,执行defer函数,defer函数没有对返回值r进行修改,只是修改了变量t
    func() {
        t = t + 5
    }()
    return
}

返回值:5

func f() (r int) {
    defer func(r int) {
        r = r + 5
    }(r)
    return 1
}

func f() (r int) {
    //给返回值赋值
    r = 1
    /**
     * 这里修改的r是函数形参的值,是外部传进来的
     * func(r int){}里边r的作用域只该func内,修改该值不会改变func外的r值
     */
    func(r int) {
        r = r + 5
    }(r)
    return
}

返回值:1
func main() {
    i := 0
    defer fmt.Println("a:", i)
    //传参数,不会改变i的值(因为传的是值,不是引用),如上边的例3
    defer func(i int) {
        fmt.Println("b:", i)
    }(i)
    //闭包调用,捕获同作用域下的i进行计算
    defer func() {
        fmt.Println("c:", i)
    }()
    i++
}

返回值:(实时传参)
c: 1
b: 0
a: 0

defer配合recover

  • recover(异常捕获)可以让程序在引发panic的时候不会崩溃退出。
  • 在引发panic的时候,panic会停掉当前正在执行的程序,但是,在这之前,它会有序的执行完当前goroutine的defer列表中的语句。
  • 所以我们通常在defer里面挂一个recover,防止程序直接挂掉,类似于try…catch,但绝对不能像try…catch这样使用,因为panic的作用不是为了抓异常。recover函数只在defer的上下文中才有效,如果直接调用recover,会返回nil。
  • Panic是一个内置的函数: 停止当前控制流, 然后开始panicking. 当F函数调用panic,F函数将停止执行后续的普通语句, 但是之前的defered函数调用仍然被正常执行, 然后再返回到F的调用者. 对于F函数的调用者, F 的行为和直接调用panic函数类似. 以上的处理流程会一直沿着调用栈回朔, 直到 当前的goroutine返回引起程序崩溃! Panics可以通过直接调用panic方式触发, 也可以由某些运行时 错误触发, 例如: 数组的越界访问.
  • Recover也是一个内置函数: 用于从panicking恢复.Recover和defer配合使用会非常有用. 对于一个普通的执行流程, 调用recover将返回nil, 也没有任何效果。但如果当前goroutine处于panicking状态,recover调用会捕获触发panic时的参数, 并且恢复到正常的执行流程.

总结:recover可以捕获panic,一般在同一个函数里在defer里进行recover捕获,如果出现panic,则直接停止往下执行,调用本函数里的defer,如果本函数里的defer没有recover捕获,则会一直向上直到调用处,在这个过程中由于没有捕获,则每个调用函数后面的语句都不会执行,如果调用处最终捕获,则可以收到错误,但是调用处往下依旧不能执行,所以如果想要所有函数的正常执行,出现panic错误不退出的话,则在每个函数或者协程的defer 里加一个recover捕获异常。

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)
}
输出

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.
  • Recovered in f 4 的4是产生panic时的调用参数,即g(4)。
  • 注意没有 Returned normally from g. 这一句,panic会波及调用者,使得调用者也停止执行后续语句。
  • panic传递g(4) –> g(3) –> g(2) –> g(1) –> g(0) –> f(),所以f函数最后一行的打印不执行。f中的recover使得panic不再向上传递,所以f的调用者main函数正常退出。
  • 如果我们从函数f中移除deferred语句,panic在扩散到goroutine栈顶前将不会被捕获, 最终会引起 程序崩溃. 下面是修改后的输出结果
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]