我正在阅读The Go Programming Language一书及其对错误包和界面的描述
package errors
type error interface {
Error() string
}
func New(text string) error { return &errorString{text} }
type errorString struct { text string }
func (e *errorString) Error() string { return e.text }
它说
errorString的基础类型是结构,而不是字符串,以保护其表示免受无意(或预谋)更新。
这是什么意思?由于errorString
未导出,因此包不会隐藏基础类型吗?
更新
以下是我使用errorString
实现string
时使用的测试代码。请注意,当尝试从其他包中使用它时,您不能仅将字符串指定为错误。
package testerr
type Error interface {
Error() string
}
func New(text string) Error {
return errorString(text)
}
type errorString string
func (e errorString) Error() string { return string(e) }
使用建议的代码进行测试
func main() {
err := errors.New("foo")
err = "bar"
fmt.Prinln(err)
}
编译时最终会产生错误
cannot use "bar" (type string) as type testerr.Error in assignment:
string does not implement testerr.Error (missing Error method)
当然对此有一个缺点,因为碰巧有相同错误字符串的不同错误将评估为我们不想要的相等。
答案 0 :(得分:5)
本书的解释"保护代表免受无意更新"看起来误导了我。无论errorString
是结构还是字符串,错误消息仍然是字符串,字符串是immutable by specification。
这也不是关于独特性的争论。例如,errors.New("EOF") == io.EOF
计算结果为false
,尽管两个错误都具有完全相同的基础消息。即使errorString
是字符串,同样适用也是如此,只要errors.New
返回指向它的指针(see my example。)
你可以说实现error
的结构是惯用的,因为标准库也引入了自定义错误。请查看SyntaxError
包中的encoding/json
:
type SyntaxError struct {
Offset int64 // error occurred after reading Offset bytes
// contains filtered or unexported fields
}
func (e *SyntaxError) Error() string { return e.msg }
(source)
此外,实现error
接口的结构体没有性能影响,并且不会在字符串实现上消耗更多内存。请参阅Go Data Structures。
答案 1 :(得分:3)
您的testerr包工作得很好,但它失去了“基于结构的”标准错误包的一个主要特性:不平等:
package main
import ( "fmt"; "testerr"; "errors" )
func main() {
a := testerr.New("foo")
b := testerr.New("foo")
fmt.Println(a == b) // true
c := errors.New("foo")
d := errors.New("foo")
fmt.Println(c == d) // false
}
errorString
是一个普通字符串,具有相同字符串内容的不同错误变得相等。原始代码使用指向struct的指针,每个New
分配一个新结构,因此如果与New
进行比较,==
返回的不同值是不同的,尽管是相同的错误文本。
此处不允许编译器生成相同的指针。 “对New的不同调用产生不同的错误值”的这一特性对于防止意外的错误相等非常重要。通过*errorString
实现Error
,可以修改您的测试人员以产生此属性。试一试:你需要一个临时的地址。它“感觉”错了。可以想象一个花哨的编译器内部化字符串值并可能返回相同的指针(因为它指向相同的内部化字符串),这将打破这个不错的不等式属性。