为什么正常返回可以隐藏命名返回正确提供给调用者的恐慌?

ved*_*ran 6 go

package main

import (
    "fmt"
    "log"
)

func catch(err *error) {
    if r := recover(); r != nil {
        *err = fmt.Errorf("recovered panic: %v", r)
    }
}

func panicIf42(n int) {
    if n == 42 {
        panic("42!")
    }
}

func NormalReturns(n int) error {
    var err error
    defer catch(&err)
    panicIf42(n)
    return err
}

func NamedReturns(n int) (err error) {
    defer catch(&err)
    panicIf42(n)
    return
}

func main() {
    err := NamedReturns(42)
    log.Printf("NamedReturns error: %v", err)
    err = NormalReturns(42)
    log.Printf("NormalReturns error: %v", err)
}
Run Code Online (Sandbox Code Playgroud)

输出:

2009/11/10 23:00:00 NamedReturns error: recovered panic: 42!
2009/11/10 23:00:00 NormalReturns error: <nil>
Run Code Online (Sandbox Code Playgroud)

游乐场链接

NormalReturns 返回一个 nil 错误,但我希望 NamedReturns 和 NormalReturns 都返回一个非 nil 错误。

我认为命名返回只是一个代码可读性功能,它为您声明和初始化返回,但似乎还有更多。我错过了什么?

icz*_*cza 6

我认为命名返回只是一个代码可读性功能,它为您声明和初始化返回,但似乎还有更多。我错过了什么?

如果对结果参数进行命名,它们在返回给调用者时的实际值将决定返回值。这意味着您可以像其他局部变量一样更改它们的值,如果return语句的表达式列表为空,则将使用它们最后分配的值。此外,如果有延迟功能,它们可以修改命名的结果参数的值return声明,之前的函数返回到其调用者,这些修改将被保留。它还允许在发生紧急情况时修改返回值,请参阅如何在发生紧急情况的 Go 函数中返回值?

规格:返回语句:

不管它们[返回值]是如何声明的,所有结果值在进入函数时都被初始化为其类型的零值。指定结果的“返回”语句在执行任何延迟函数之前设置结果参数。

Spec: Defer 语句:

例如,如果延迟函数是函数字面量并且周围的函数具有在字面量范围内的命名结果参数,则延迟函数可以在返回结果参数之前访问和修改结果参数。

In NormalReturns():返回值初始化为零值(nil适用于所有接口类型,包括内置error类型),并且由于return未到达该语句(由于恐慌 in panicIf42()),它将保持nil. 局部变量err是否改变并不重要,那不是结果变量。它只是一个普通的变量。它不会影响返回的值

一般来说,如果一个函数没有命名的结果变量,并且如果这个函数没有达到一个 return语句(例如由于恐慌),它不能有除(意味着不同于)结果类型的零值之外的返回值。

NamedReturns()deferred 中catch()会修改命名的结果变量err。更改是“保留的”:函数结束时将返回命名结果变量保存的任何内容(这发生在调用延迟函数之后,如果有的话)。因此,即使return此处也未到达该语句,该catch()函数也会更改err结果变量,并且分配给它的任何内容都将用作返回值。

有关该主题的更多信息:

去博客:推迟、恐慌和恢复:

延迟函数可以读取并分配给返回函数的命名返回值。

并且在Effective Go:Recover 中:

如果发生doParse恐慌,恢复块会将返回值设置为——nil延迟函数可以修改命名返回值。