为什么使用`go panic recover`来返回带有局部变量的值不起作用?

huangapple go评论87阅读模式
英文:

Why does go panic recover to return value with local variable not work?

问题

这是一个使用命名返回值的恢复恐慌代码

func main() {
    result, err := foo()
    fmt.Println("result:", result)
    if err != nil {
        fmt.Println("err:", err)
    }
}

func foo() (result int, err error) {
    defer func() {
        if e := recover(); e != nil {
            result = -1
            err = errors.New(e.(string))
        }
    }()
    bar()

    result = 100
    err = nil
    return
}

func bar() {
    panic("panic happened")
}

输出结果为:

result: -1
err: panic happened

但是为什么使用局部变量的这段代码不起作用呢?

func main() {
    result, err := foo()
    fmt.Println("result:", result)
    if err != nil {
        fmt.Println("err:", err)
    }
}

func foo() (int, error) {
    var result int
    var err error
    defer func() {
        if e := recover(); e != nil {
            result = -1
            err = errors.New(e.(string))
        }
    }()
    bar()

    result = 100
    err = nil
    return result, err
}

func bar() {
    panic("panic happened")
}

输出结果为:

result: 0

有什么解释可以帮助我理解原因/基本概念吗?在 Go 之旅的基础部分中,解释如下。

命名返回值
Go 的返回值可以被命名。如果是这样,它们被视为在函数顶部定义的变量。

所以它应该是相同的,对吗?

英文:

This panic recover code works with named return values.

func main() {
	result, err := foo()
	fmt.Println("result:", result)
	if err != nil {
		fmt.Println("err:", err)
	}
}

func foo() (result int, err error) {
	defer func() {
		if e := recover(); e != nil {
			result = -1
			err = errors.New(e.(string))
		}
	}()
	bar()

	result = 100
	err = nil
	return
}

func bar() {
	panic("panic happened")
}

Output

result: -1
err: panic happened

But why this code with local variables does not work?

func main() {
	result, err := foo()
	fmt.Println("result:", result)
	if err != nil {
		fmt.Println("err:", err)
	}
}

func foo() (int, error) {
	var result int
	var err error
	defer func() {
		if e := recover(); e != nil {
			result = -1
			err = errors.New(e.(string))
		}
	}()
	bar()

	result = 100
	err = nil
	return result, err
}

func bar() {
	panic("panic happened")
}

Output

result: 0

Any explanation to help me understanding the reason / basic concept of it? In the go tour basics the explanation is as followed.
> Named return values
Go's return values may be named. If so, they are treated as variables defined at the top of the function.

So it should be the same, right?

答案1

得分: 6

请注意,这与 panic/recover 没有任何关系,这是 defer 语句的一个特性。

如果延迟函数是一个函数字面量,并且包围的函数具有命名结果参数,在字面量内部可以访问和修改结果参数,在它们被返回之前。如果延迟函数有任何返回值,在函数完成时它们将被丢弃。

英文:

Note that this has nothing to do with panic/recover, it is a feature of the defer statement.

> ... if the deferred function is a function literal and the surrounding
> function has named result parameters that are in scope within the
> literal, the deferred function may access and modify the result
> parameters before they are returned. If the deferred function has
> any return values, they are discarded when the function completes.

答案2

得分: 5

Spec: Return statements详细说明了以下内容:

  1. 有三种方法可以从具有结果类型的函数中返回值:
    • 返回语句中可以明确列出返回值。每个表达式必须是单值的,并且可以赋值给函数结果类型的相应元素。
    • 返回语句中的表达式列表可以是对多值函数的单个调用。效果就好像从该函数返回的每个值都被分配给具有相应类型的临时变量,然后在这些变量上列出一个返回语句,此时将应用前面情况的规则。
    • 如果函数的结果类型为其结果参数指定了名称,则表达式列表可以为空。结果参数的作用类似于普通的局部变量,函数可以根据需要为它们赋值。返回语句返回这些变量的值。

因此,如果使用明确列出返回值的return语句,那么这些值将被使用,无论结果参数是否具有名称。

如果结果参数具有名称,它们将作为普通的局部变量:您可以读取和写入它们。如果结果参数具有名称,您可以使用“裸露”的return语句,而无需列出要返回的值。如果这样做,实际的返回值将是(具名)结果参数的值。如果函数由于发生恐慌和恢复而未达到return语句,那么一旦延迟函数运行,实际的返回值将是具名结果参数的值(延迟函数可以更改这些值并“发表意见”以确定返回值)。

如果您不使用具名结果参数,而是声明局部变量,它们在这方面不具有特殊性:当函数返回时,它们不会“自动”用作结果值(就像它们如果是具名结果参数而不是局部变量时那样)。因此,如果您在延迟函数中更改它们,那将不会对实际返回的值产生任何影响。实际上,如果您不使用具名结果参数并且函数发生恐慌和恢复,您无法指定返回值,它们将是结果类型的零值。这就是为什么您看到result: 00int的零值),而没有错误(因为error是接口类型,接口类型的零值是nil,如果nil则不打印错误)。

参考链接:https://stackoverflow.com/questions/33167282/how-to-return-a-value-in-a-go-function-that-panics/33167433#33167433

英文:

Spec: Return statements details this:

> There are three ways to return values from a function with a result type:
> 1. The return value or values may be explicitly listed in the "return" statement. Each expression must be single-valued and assignable to the corresponding element of the function's result type.
> 2. The expression list in the "return" statement may be a single call to a multi-valued function. The effect is as if each value returned from that function were assigned to a temporary variable with the type of the respective value, followed by a "return" statement listing these variables, at which point the rules of the previous case apply.
> 3. The expression list may be empty if the function's result type specifies names for its result parameters. The result parameters act as ordinary local variables and the function may assign values to them as necessary. The "return" statement returns the values of these variables.

So basically if you use a return statement that explicitly lists the return values, those will be used, regardless if the result parameters are named or not.

If the result parameters are named, they act as ordinary local variables: you can read and write them. If the result parameters are named, you may use a "naked" return statement, without listing the values to return. If you do so, then the actual return values will be the values of the (named) result parameters. The same thing applies if your function does not reach a return statement due to panicing and recovering: once the deferred functions run, the actual return values will be the values of the named result parameters (which the deferred functions can change and "have a say" in what to return).

If you don't use named result parameters but you declare local variables, they are not special in this way: when the function returns, those are not used "automatically" as the result values (like they would be if they would be named result parameters and not local variables). So if you change them in a deferred function, that will not have any effect on the actual values returned. In fact, if you don't use named result parameters and your function panics and recovers, you can't specify the return values, they will be the zero values of the result types. That's why you see result: 0 (0 is the zero value for int) and no error (because error is an interface type and zero value for interface types is nil and you don't print the error if it's nil).

See related: https://stackoverflow.com/questions/33167282/how-to-return-a-value-in-a-go-function-that-panics/33167433#33167433

答案3

得分: 0

可能是对@icza回答的简要总结:

  1. 命名返回变量在函数正常返回或从panic中恢复时,会使用它们的最终值进行返回。因此,你可以在defer recover func()中更改它们的值,最终的值将成为返回值。
  2. 如果使用局部变量作为返回变量,编译器无法知道这些局部变量将被用作返回变量,直到发生正常返回。局部变量可能在panic恢复中被更改,但由于panic的原因,return语句尚未执行,所以你定义的局部变量不会被视为返回变量,返回值将是返回类型的零值。
英文:

Might be a brief summary for @icza's anwser:

  1. Named return variables use their final values for returning when the function teminate with no panic(return normally or recover from panic), so you can change them in defer recover func(), and the final values changed, so be the return values.
  2. If use local variables, compiler can not know these local variables will be used as return variables until a normal return. Local variables might be changed in panic recover, but
    the return statement has not been executed yet because the panic, so the local variables you defined was not treated as return variables, the return values will be the zero values of the return types.

huangapple
  • 本文由 发表于 2021年7月28日 13:42:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/68554968.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定