在网上和堆栈溢出中读取值接收器与指针接收器,我理解基本规则:如果你不打算修改接收器,接收器相对较小,则不需要指针
然后,阅读有关实现error
接口(例如https://blog.golang.org/error-handling-and-go)的内容,我看到Error()
函数的示例都使用了指针接收器。
然而,我们没有修改接收器,结构非常小。
我觉得没有指针(return &appError{}
vs return appError{}
),代码就更好了。
这些示例是否有使用指针的原因?
答案 0 :(得分:7)
首先,您关联的博文和您的示例,appError
不是error
。它是载体的错误值以及示例的实现使用的其他相关信息的包装器,它们不会公开,也不会appError
也不会公开*appError
曾被用作error
值。
因此,您引用的示例与您的实际问题无关。但要回答标题中的问题:
一般来说,一致性可能是原因。如果一个类型有很多方法而一些需要指针接收器(例如因为它们修改了值),通常用指针接收器声明所有方法是有用的,所以对method sets没有混淆类型和指针类型。
关于error
实现的回答:当您使用struct
值来实现error
值时,使用非指针实现{{1}会很危险接口。为什么会这样?
因为error
是一个界面。接口值为comparable。并通过比较它们包装的值来比较它们。 你会得到不同的比较结果,基于它们包含的值/类型!因为如果你在其中存储指针,如果它们存储相同的指针,错误值将是相等的。如果你在其中存储非指针(结构),如果结构值相等则它们是相等的。
详细说明并举例说明:
标准库包含errors
个包。您可以使用errors.New()
函数从error
值创建错误值。如果你看一下它的实现(errors/errors.go
),那很简单:
string
该实现返回一个指向非常简单的struct值的指针。这样,如果您创建具有相同// 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
}
值的2个错误值,则它们不会相等:
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
}
的2个错误值将相等:
string
输出:
e1 = New("hey")
e2 = New("hey")
fmt.Println(e1, e2, e1 == e2)
尝试Go Playground上的示例。
为什么这很重要的一个很好的例子:查看存储在变量io.EOF
中的错误值:
hey hey true
期望io.Reader
实现将此特定错误值返回到信号输入结束。因此,您可以和平地将var EOF = errors.New("EOF")
返回的错误与Reader.Read()
进行比较,以判断是否已达到输入结束。您可以确定,如果它们偶尔返回自定义错误,它们将永远不会等于io.EOF
,这是io.EOF
保证的(因为它返回指向未导出的struct值的指针)。
答案 1 :(得分:1)
go中的错误仅满足错误接口,即提供.Error()
方法。创建自定义错误或挖掘Go源代码,您会发现错误更多的是在幕后。如果在应用程序中填充了结构,为避免在内存中进行复制,将其作为指针传递会更有效。此外,如The Go Programming Language一书中所示:
fmt.Errorf函数使用fmt.Sprintf格式化错误消息并返回新的错误值。我们通过将其他上下文信息连续添加到原始错误消息中来使用它来构建描述性错误。当错误最终由程序的主要功能处理时,它应该提供从根本问题到整体失败的清晰因果链,让人想起NASA事故调查:
genesis: crashed: no parachute: G-switch failed: bad relay orientation
由于错误消息经常链接在一起,因此不应将消息字符串大写,并且应避免使用换行符。产生的错误可能很长,但是当像grep。
这样的工具找到时,它们将是自包含的
从这里我们可以看到,如果单一的错误类型'拥有丰富的信息,除此之外,我们还有“链接”信息。他们一起创建详细的消息,使用指针将是实现这一目标的最佳方式。
答案 2 :(得分:0)
没有:)
https://blog.golang.org/error-handling-and-go#TOC_2
Go接口允许任何符合错误接口的内容由期望error
的代码处理
type error interface {
Error() string
}
就像你提到的那样,如果你不打算修改状态,就没有动力去传递指针:
随机咆哮,有趣的是,我个人认为看到像这样的例子是为什么新的程序员默认支持指针接收器。
答案 3 :(得分:0)
我们可以从错误处理的角度来看这个问题,而不是错误的产生。
错误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
时。
因此,通过使用指针接收器不会阻止用户将指针作为错误返回。
话虽如此,所有其他方面,例如 堆栈与堆(内存分配,GC压力)仍然适用。
根据用例选择实现。
通常,由于我上面演示的原因,我更喜欢指针接收器。我更喜欢Friendly API而不是性能,有时候,当错误类型包含大量信息时,它会表现得更好。
答案 4 :(得分:0)
环聊说明了指针接收器很好的一般原因:
https://tour.golang.org/methods/8
使用指针接收器有两个原因。
第一个是这样,该方法可以修改其接收者指向的值。
通常,给定类型上的所有方法都应具有值或指针接收器,但不能同时包含两者。