golang和指针接收器中的自定义错误 [英] Custom errors in golang and pointer receivers
问题描述
阅读有关Web和stackoverflow上的值接收器与指针接收器的信息,我了解以下基本规则:如果您不打算修改接收器,并且接收器相对较小,则不需要指针. /p>
然后,阅读有关实现error
接口的信息(例如 https://blog.golang.org/error-handling-and-go ),我看到Error()
函数的示例全部使用指针接收器.
但是,我们没有修改接收器,结构非常小.
我觉得没有指针(return &appError{}
与return appError{}
)的代码要好得多.
示例使用指针有理由吗?
首先,您链接并作为示例的博客文章appError
不是不是,而是error
.这是一个包装程序,错误地携带一个错误值以及示例的实现所使用的其他相关信息,它们没有公开,并且appError
或*appError
都不存在用作error
值.
因此,您引用的示例与您的实际问题无关.但是要回答标题中的问题:
通常,一致性可能是原因.如果一个类型有很多方法并且有些需要指针接收器(例如,因为它们修改了值),通常使用指针接收器声明所有方法很有用,因此对于 error
是一个接口.接口值是可比的.通过比较它们包装的值来比较它们. 然后您将得到不同的比较结果,这是基于其中包装了哪些值/类型!因为如果在其中存储指针,则如果它们存储相同的指针,则错误值将相等.而且,如果将非指针(结构)存储在它们中,则在结构值相等的情况下它们也是相等的.
对此进行详细说明并显示一个示例:
标准库具有 errors
包.您可以使用 errors.New()
函数从string
值创建错误值.如果您看一下它的实现( 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
值的错误值,它们将不相等:
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
在转到游乐场上尝试示例.
var EOF = errors.New("EOF")
预计 io.Reader
实现会返回此特定错误值,以表示已结束输入.因此,您可以和平地比较Reader.Read()
和io.EOF
返回的错误,以判断是否到达输入结束.您可以确定,如果它们偶尔返回自定义错误,它们将永远不会等于io.EOF
,这正是errors.New()
所保证的(因为它返回了指向未导出结构值的指针).
Reading about value receivers vs pointer receivers across the web and stackoverflow, I understand the basic rule to be: If you don't plan to modify the receiver, and the receiver is relatively small, there is no need for pointers.
Then, reading about implementing the error
interface (eg. https://blog.golang.org/error-handling-and-go), I see that examples of the Error()
function all use pointer receiver.
Yet, we are not modifying the receiver, and the struct is very small.
I feel like the code is much nicer without pointers (return &appError{}
vs return appError{}
).
Is there a reason why the examples are using pointers?
First, the blog post you linked and took your example from, appError
is not an error
. It's a wrapper that carriers an error value and other related info used by the implementation of the examples, they are not exposed, and not appError
nor *appError
is ever used as an error
value.
So the example you quoted has nothing to do with your actual question. But to answer the question in title:
In general, consistency may be the reason. If a type has many methods and some need pointer receiver (e.g. because they modify the value), often it's useful to declare all methods with pointer receiver, so there's no confusion about the method sets of the type and the pointer type.
Answering regarding error
implementations: when you use a struct
value to implement an error
value, it's dangerous to use a non-pointer to implement the error
interface. Why is it so?
Because error
is an interface. And interface values are comparable. And they are compared by comparing the values they wrap. And you get different comparison result based what values / types are wrapped inside them! Because if you store pointers in them, the error values will be equal if they store the same pointer. And if you store non-pointers (structs) in them, they are equal if the struct values are equal.
To elaborate on this and show an example:
The standard library has an errors
package. You can create error values from string
values using the errors.New()
function. If you look at its implementation (errors/errors.go
), it's simple:
// 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
}
The implementation returns a pointer to a very simple struct value. This is so that if you create 2 error values with the same string
value, they won't be equal:
e1 := errors.New("hey")
e2 := errors.New("hey")
fmt.Println(e1, e2, e1 == e2)
Output:
hey hey false
This is intentional.
Now if you would return a non-pointer:
func New(text string) error {
return errorString{text}
}
type errorString struct {
s string
}
func (e errorString) Error() string {
return e.s
}
2 error values with the same string
would be equal:
e1 = New("hey")
e2 = New("hey")
fmt.Println(e1, e2, e1 == e2)
Output:
hey hey true
Try the examples on the Go Playground.
A shining example why this is important: Look at the error value stored in the variable io.EOF
:
var EOF = errors.New("EOF")
It is expected that io.Reader
implementations return this specific error value to signal end of input. So you can peacefully compare the error returned by Reader.Read()
to io.EOF
to tell if end of input is reached. You can be sure that if they occasionally return custom errors, they will never be equal to io.EOF
, this is what errors.New()
guarantees (because it returns a pointer to an unexported struct value).
这篇关于golang和指针接收器中的自定义错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!