英文:
Why is errorString a struct, not a string
问题
我正在阅读《Go编程语言》这本书,在其中对错误包和接口进行描述。代码部分如下:
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
没有被导出,包不就隐藏了底层类型吗?
更新
以下是我使用 string
实现 errorString
的测试代码。请注意,当尝试从另一个包中使用它时,你不能将一个字符串直接赋值给一个错误。
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.Println(err)
}
在编译时会产生错误:
cannot use "bar" (type string) as type testerr.Error in assignment: string does not implement testerr.Error (missing Error method)
当然,这样做也有一个缺点,即不同的错误如果具有相同的错误字符串,将被认为是相等的,而我们并不希望这样。
英文:
I am reading The Go Programming Language book and in it's description of the error package and the interface
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 }
it says
> The underlying type of errorString is a struct, not a string, to protect its representation from inadvertent (or premeditated) updates.
What does this mean? Wouldn't the package hide the underlying type since errorString
isn't exported?
Update
Here is the test code I used implementing errorString
using a string
instead. Note that when try to use it from another package, you can't just assign a string as an error.
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) }
And testing it with the suggested codes
func main() {
err := errors.New("foo")
err = "bar"
fmt.Prinln(err)
}
Will end up producing an error when compiling
cannot use "bar" (type string) as type testerr.Error in assignment:
string does not implement testerr.Error (missing Error method)
Of course there is a downside to this since different errors that happen to have the same error string will evaluate to being equal which we don't want.
答案1
得分: 7
关于“保护表示免受意外更新”的解释对我来说似乎是误导性的。无论errorString
是一个结构体还是一个字符串,错误消息仍然是一个字符串,并且根据规范,字符串是不可变的(参考规范)。
这也不是关于唯一性的辩论。例如,errors.New("EOF") == io.EOF
的结果是false
,尽管这两个错误具有完全相同的底层消息。即使errorString
是一个字符串,只要errors.New
返回指向它的指针(参见我的示例),情况也是如此。
你可以说实现error
接口的结构体是惯用的,因为这也是标准库引入自定义错误的方式。看一下encoding/json
包中的SyntaxError
:
type SyntaxError struct {
Offset int64 // 在读取 Offset 字节后发生错误
// 包含过滤或未导出的字段
}
func (e *SyntaxError) Error() string { return e.msg }
(来源)
此外,实现error
接口的结构体对性能没有影响,也不会消耗更多的内存,详见Go数据结构。
英文:
The book's explanation about "protecting representation from inadvertent updates" looks misleading to me. Whether errorString
is a struct or a string, the error message is still a string and a string is immutable by specification.
This isn't a debate about uniqueness either. For example, errors.New("EOF") == io.EOF
evaluates to false
, although both errors have the exact same underlying message. The same would apply even if errorString
was a string, as long as errors.New
would return a pointer to it (see my example.)
You could say a struct implementing error
is idiomatic since that's also how the standard library introduces custom errors. Take a look at SyntaxError
from the encoding/json
package:
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)
Also, a struct implementing the error
interface has no performance implications and does not consume more memory over a string implementation. See Go Data Structures.
答案2
得分: 5
你的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
作为普通字符串,具有相同字符串内容的不同错误将变为相等。原始代码使用指向结构体的指针,每个New
都分配一个新的结构体,因此如果使用==
进行比较,从New
返回的不同值是不同的,尽管错误文本相等。
在这里,没有编译器被允许产生相同的指针。而“不同的New调用产生不同的错误值”这个特性很重要,可以防止错误的意外相等。你可以修改你的testerr来实现这个特性,让*errorString
实现Error
接口。试试看:你需要一个临时变量来取地址。这样做“感觉”不对。可以想象一种高级的编译器,它内部化字符串值,并可能返回相同的指针(因为它指向相同的内部化字符串),这将破坏这个好的不等式特性。
英文:
Your testerr package works pretty well but it looses a major feature of the "struct-based" standard error package: That of un-equality:
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
}
With errorString
being a plain string different errors with the same string content become equal. The original code uses a pointer to struct and each New
allocates a new struct so the different values returned from New
are different if compared with ==
albeit the equal error text.
No compiler is allowed to produce the same pointer here. And this feature of "different calls to New produce different error values" is important to prevent unintended equality of errors. Your testerr can be modified to yield this property by having *errorString
implement Error
. Try it: You need a temporary to take the address of. It "feels" wrong. One could imagine a fancy compiler which internalises the string values and might return the same pointer (as it points to the same internalised string) which would break this nice inequality property.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论