golang和指针接收器中的自定义错误

问题描述 投票:5回答:5

通过Web和stackoverflow读取值接收器与指针接收器,我理解基本规则:如果你不打算修改接收器,接收器相对较小,则不需要指针。

然后,阅读关于实现error接口(例如,https://blog.golang.org/error-handling-and-go),我看到Error()函数的示例都使用指针接收器。

然而,我们没有修改接收器,结构非常小。

我觉得没有指针(return &appError{} vs return appError{})代码更好。

这些示例是否有使用指针的原因?

pointers go error-handling pass-by-reference
5个回答
8
投票

首先,你链接的博客文章和你的例子,appError不是error。它是一个包含错误值的包装器和示例实现所使用的其他相关信息,它们没有公开,而且appError*appError也没有被用作error值。

因此,您引用的示例与您的实际问题无关。但要回答标题中的问题:

一般来说,一致性可能是原因。如果一个类型有很多方法而且有些需要指针接收器(例如因为它们修改了值),通常用指针接收器声明所有方法是有用的,所以对于类型的method sets和指针类型没有混淆。

关于error实现的答案:当你使用struct值来实现error值时,使用非指针来实现error接口是危险的。为什么会这样?

因为error是一个界面。接口值是comparable。并通过比较它们包装的值来比较它们。并根据包含在其中的值/类型得到不同的比较结果!因为如果在其中存储指针,如果它们存储相同的指针,则错误值将相等。如果你在其中存储非指针(结构),如果结构值相等,它们是相等的。

详细说明并举例说明:

标准库有一个errors包。您可以使用string函数从errors.New()值创建误差值。如果你看一下它的实现(errors/errors.go),它很简单:

// Package errors implements functions to manipulate errors.
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

该实现返回一个指向非常简单的struct值的指针。这样,如果您使用相同的string值创建2个错误值,它们将不相等:

e1 := errors.New("hey")
e2 := errors.New("hey")
fmt.Println(e1, e2, e1 == e2)

输出:

hey hey false

这是故意的。

现在,如果你要返回一个非指针:

func New(text string) error {
    return errorString{text}
}

type errorString struct {
    s string
}

func (e errorString) Error() string {
    return e.s
}

具有相同string的2个错误值将相等:

e1 = New("hey")
e2 = New("hey")
fmt.Println(e1, e2, e1 == e2)

输出:

hey hey true

试试Go Playground上的例子。

一个很好的例子,为什么这很重要:查看存储在变量io.EOF中的错误值:

var EOF = errors.New("EOF")

预期io.Reader实现将此特定错误值返回到信号输入结束。因此,您可以和平地将Reader.Read()返回的错误与io.EOF进行比较,以判断是否已达到输入结束。你可以肯定,如果他们偶尔会返回自定义错误,它们永远不会等于io.EOF,这就是errors.New()所保证的(因为它返回一个指向未导出的struct值的指针)。


2
投票

go中的错误仅满足错误接口,即提供.Error()方法。创建自定义错误或挖掘Go源代码,您会发现错误更多的是在幕后。如果在应用程序中填充了结构,为避免在内存中进行复制,将其作为指针传递会更有效。此外,如Go Go Programming Language一书所示:

fmt.Errorf函数使用fmt.Sprintf格式化错误消息并返回新的错误值。我们通过将其他上下文信息连续添加到原始错误消息中来使用它来构建描述性错误。当错误最终由程序的主要功能处理时,它应该提供从根本问题到整体故障的清晰因果链,让人想起NASA事故调查:

genesis: crashed: no parachute: G-switch failed: bad relay orientation

由于错误消息经常链接在一起,因此不应将消息字符串大写,并且应避免使用换行符。产生的错误可能很长,但是当像grep这样的工具找到它们时它们将是自包含的。

从中我们可以看到,如果单个“错误类型”包含大量信息,并且除此之外我们将它们“链接”在一起以创建详细消息,使用指针将是实现此目的的最佳方式。


1
投票

我们可以从错误处理的角度来看这个,而不是创建错误。

错误Definiton Side的故事

type ErrType1 struct {}

func (e *ErrType1) Error() string {
    return "ErrType1"
}

type ErrType2 struct {}

func (e ErrType2) Error() string {
    return "ErrType1"
}

错误处理程序方的故事

err :=  someFunc()
switch err.(type) {
case *ErrType1
   ...
case ErrType2, *ErrType2
   ...
default
   ...
}

如您所见,如果在值接收器上实现错误类型,那么在执行类型断言时,您需要担心这两种情况。

对于ErrType2&ErrType2{}ErrType2{}都满足界面。

因为someFunc返回一个error接口,所以你永远不知道它是返回结构值还是结构指针,特别是当你没有写someFunc时。

因此,通过使用指针接收器不会阻止用户将指针返回为错误。

话虽如此,所有其他方面,如Stack vs. Heap(内存分配,GC压力)仍然适用。

根据您的使用案例选择您的实施。

一般来说,我更喜欢指针接收器,原因我上面说明了。我更喜欢Friendly API而不是性能,有时候,当错误类型包含大量信息时,它的性能更高。


0
投票

没有:)

https://blog.golang.org/error-handling-and-go#TOC_2

Go接口允许任何符合错误接口的东西由期望error的代码处理

type error interface {
    Error() string
}

就像你提到的那样,如果你不打算修改状态,就没有动力去传递指针:

  • 分配给堆
  • GC压力
  • 可变状态和并发等

在一个随机的咆哮,有趣的是,我个人认为看到像这样的例子是为什么新的程序员默认支持指针接收器。


0
投票

go之旅很好地解释了指针接收器的一般原因:

https://tour.golang.org/methods/8

使用指针接收器有两个原因。

第一个是这个方法可以修改其接收者指向的值。

通常,给定类型的所有方法都应该具有值或指针接收器,但不能同时具有两者的混合。

© www.soinside.com 2019 - 2024. All rights reserved.