英文:
Check named return error using defer function
问题
你好,以下是翻译好的内容:
嗨,我想编写一个通用函数,在函数返回错误时跟踪错误消息。所以我写了这个函数:
func TraceError1(err *error) {
if err != nil && *err != nil {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
}
}
func TraceError2(err error) {
if err != nil {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
}
}
func foo() (err error) {
defer TraceError1(&err)
defer TraceError2(err)
fmt.Println("do something")
return fmt.Errorf("haha")
}
TraceError1
能够正常工作,但 TraceError2
不能。在我理解中,error
是一个接口,所以它是一个指针/地址,为什么我需要传递它的地址?为什么 TraceError2
不能工作?谢谢。
英文:
Hi I want to write a generic function to trace error message when a function returns error. So I wrote this:
func TraceError1(err *error) {
if err != nil && *err != nil {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
}
}
func TraceError2(err error) {
if err != nil {
pc := make([]uintptr, 15)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
}
}
func foo() (err error) {
defer TraceError1(&err)
defer TraceError2(err)
fmt.Println("do something")
return fmt.Errorf("haha")
}
TraceError1
works but TraceError2
didn't. In my understanding, error
is an interface so it is a pointer/address, why do I need to pass its address? Why TraceError2
cannot work? Thanks.
答案1
得分: 3
在TraceError1
的情况下,你传递了一个指向命名返回值err
的指针。指针是非空的,但它指向的值(err
)是空的(在defer执行时)。然而,它尚未被评估(解引用),因为TraceError1
尚未被调用。当函数运行时(在foo
返回后),指针被解引用,并且err
的值已经被更新(由foo
内的返回语句)。
然而,在TraceError2
的情况下,传递的是一个空接口值,即使TraceError2
最终执行,它仍然保持为空。
package main
import "fmt"
func intByValue(i int) {
fmt.Printf("i = %d\n", i)
// ^--- `i`是一个整数值
// --- 无论传递给函数的是什么,都会被打印出来
}
func intByRef(i *int) {
var v int = *i // i是一个指向int的指针,在这里被解引用
// 传递的是实际值所在的*地址*
// 虽然地址保持不变,但在i被解引用之前,它的值可能会发生变化,并且其值存储在v中。
fmt.Printf("i = %d\n", v)
}
func main() {
var i int
defer intByValue(i) // 传递了i的*值*,此时为0
defer intByRef(&i) // 传递了i的*指针*,其中包含0
i = 100 // 在intByRef能够“解引用”其参数之前,它所包含的值已经被更新
// 调用intByRef,解引用该值,找到100,打印它。
// 调用intByValue,找到0,打印它
// 结果应该是:
// i = 100
// i = 0
}
所以,不幸的是,如果你想在被延迟执行的函数使用之前更新错误(例如通过返回命名返回值),你必须传递指向该变量的指针。
换句话说,TraceError2
对于你的用例来说并不适用。
编辑:使用正确的术语并(有争议地)改进示例代码。
英文:
In case of TraceError1
you are passing a pointer to the named return value err
. The pointer is non-nil, but the value it points at (err
) is nil (at the time of defer). However, it is not yet evaluated (dereferenced) because TraceError1
has not yet been called. By the time the function does run (after foo
returns) and the pointer gets dereferenced, the value of err
has been updated (by the return statement inside foo
).
However, in case of TraceError2
, a nil interface value is passed, which will stay nil even when TraceError2
executes eventually.
package main
import "fmt"
func intByValue(i int) {
fmt.Printf("i = %d\n", i)
// ^--- `i` is an integer value
// --- whatever i was passed to the function, gets printed
}
func intByRef(i *int) {
var v int = *i // i is a pointer to an int, which gets dereferenced here
// the *address* where the actual value resides was passed
// while the address stays the same, its value can change before
// i is dereferenced, and its value stored in v.
fmt.Printf("i = %d\n", v)
}
func main() {
var i int
defer intByValue(i) // passed the *value* of i, which is 0 right now
defer intByRef(&i) // passed a *pointer* to i, which contains 0 right now
i = 100 // before intByRef could "dereference" its argument, the value that it
// contained has been updated
// intByRef gets called, dereferences the value, finds 100, prints it.
// intByValue gets called, finds 0, prints it
// result should be:
// i = 100
// i = 0
}
So unfortunately, if you want the ability to update the error (e.g. by returning a named return value) before it gets used by the deferred function, you are going to have to pass around pointers to the variable.
In other words, TraceError2
is simply not suited for your use case.
Edit: use correct terminology and (questionably) improve example code.
答案2
得分: 2
根据go blog的解释:
defer语句的行为是直接明了且可预测的。
它遵循三个简单的规则:
- 在defer语句被执行时,延迟函数的参数会被求值。
- 延迟函数的调用会在包围函数返回后按照后进先出的顺序执行。
- 延迟函数可以读取和赋值给返回函数的命名返回值。
根据第一个规则,当你调用defer TraceError2(err)
时,err
的值为nil
,并且这个值会传递给TraceError2
函数。
TraceError1(err *error)
之所以有效,是因为它获取了一个指向err
的指针,并且在执行defer func TraceError1
之前,该指针的值已经被赋值。
下面是一个简单的示例代码来解释这种行为。
package main
import (
"fmt"
"runtime"
)
func main() {
i := 0
defer func(i int) {
fmt.Printf("%d\n", i) //输出:0
}(i)
defer func(i *int) {
defer fmt.Printf("%d\n", *i) //输出:1
}(&i)
i++
}
英文:
As go blog explained
> The behavior of defer statements is straightforward and predictable.
> There are three simple rules:
>
> 1. A deferred function's arguments are evaluated when the defer statement is evaluated.
> 2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
> 3. Deferred functions may read and assign to the returning function's named return values.
According to first point, when you call defer TraceError2(err)
, that err = nil
and that is the value pass to the TraceError2
function.
TraceError1(err *error)
works because it is getting a pointer to err, and that pointer value is assigned before defer func TraceError1
is executed.
Simple example code to explain the behaviour.
package main
import (
"fmt"
"runtime"
)
func main() {
i := 0
defer func(i int) {
fmt.Printf("%d\n",i) //Output: 0
}(i)
defer func(i *int) {
defer fmt.Printf("%d\n",*i) //Output: 1
}(&i)
i++
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论