Go 语言里 defer, panic 和 named return values

Go 语言里,函数过程中通过 defer 注册的回调,会在函数结束前、按 LIFO 的顺序调用执行。有名返回值(named return value)在编程时,恰当的变量名,结合类型,对于走读代码、或者生成的文档,会更加清晰和直观。

当两者混用的时候,要注意相关步骤调用的先后顺序对实际返回值的影响。

先说结论:

  1. 如果是普通的返回值:
    • 函数结束时 return 的值,会被放到 return value 里;如果因为 panic() 异常而没有走到 return 的代码,return value 应该是返回值类型对应的默认值;
    • defer 回调里对变量的操作,不影响 return value
  2. 如果是 named return value,所有作用在返回值变量上的操作,等同于 return value
  3. caller 拿到 return value 里的返回值;

1. 比如:

func test() int {
    ret := 1

    defer func() {
            ret += 3
    }()

    defer func() {
            ret *= 2
    }()

    fmt.Sprintf("test .. ret=%d", ret)

    return ret
}
  1. return ret,把当前值 1 放到 return value 里;
  2. 运行两个 defer 函数,先运行 ret *= 2 得到 2,再运行 ret += 3 得到 5;但 ret 只是临时变量,不影响 return value
  3. return value 里的值交给 caller。

所以,这里返回的是 1。

2. 作为对比:

func test() (ret int) {
    ret = 1

    defer func() {
            ret += 3
    }()

    defer func() {
            ret *= 2
    }()

    fmt.Sprintf("test .. ret=%d", ret)

    return ret
}

现在 ret 是 named return value

  1. return ret 把当前值 1 放到 return value(即 ret)里;
  2. 运行两个 defer 函数,先运行 ret *= 2 得到 2,再运行 ret += 3 得到 5。这里对 ret 的修改,等同于影响 return value
  3. return value (即 ret)里的值交给 caller。

所以,这里返回的是 5。

3. 添加 panic

func test() (ret int) {
    ret = 1

    defer func() {
        if r := recover(); r != nil {
            ret *= 20
        }
    }()

    defer func() {
        if r := recover(); r != nil {
            ret *= 10
        }
    }()

    defer func() {
            ret += 3
    }()

    defer func() {
            ret *= 2
    }()

    panic("some error")

    fmt.Sprintf("test .. ret=%d", ret)

    return ret
}

在前一个例子的基础上,添加了 panicrecover 的处理。

  1. panic("some error") 后面的代码执行不到了,不过这时 return value 里已经是 1;
  2. 运行几个 defer 函数,先运行 ret *= 2 得到 2,运行 ret += 3 得到 5,再运行 recover() 一段里的 ret *= 10 得到 50,最后的 recover() 一段,因为这时 r == nil,就不执行了。这里对 ret 的修改,等同于影响 return value
  3. return value (即 ret)里的值交给 caller。

所以,这里返回的是 50。

另外,如果这里的 ret 不是 named return value,而是临时变量,那么即使变量在初始时赋值为 1,因为 panic("some error") 后面的代码执行不到,所以这时 return value 里仍然是默认值 0;并且几个 defer 函数里的修改作用在临时变量上,并不涉及到 return value,所以最后返回的是 0。

Read More: