使用延迟函数检查命名返回错误。

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

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.

Here is a simpler example:

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语句的行为是直接明了且可预测的。
它遵循三个简单的规则:

  1. 在defer语句被执行时,延迟函数的参数会被求值。
  2. 延迟函数的调用会在包围函数返回后按照后进先出的顺序执行。
  3. 延迟函数可以读取和赋值给返回函数的命名返回值。

根据第一个规则,当你调用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++
}

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

发表评论

匿名网友

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

确定