关于go:为什么errorString是结构而不是字符串

 2020-01-14 

Why is errorString a struct, not a string

我正在阅读《 Go编程语言》一书,其中包含错误包和界面的说明

1
2
3
4
5
6
7
8
9
10
11
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 }

它说

The underlying type of errorString is a struct, not a string, to protect its representation from inadvertent (or premeditated) updates.

这是什么意思? 由于未导出errorString,因此包不会隐藏基础类型吗?

更新资料
这是我用来使用string来实现errorString的测试代码。 请注意,当尝试从另一个软件包中使用它时,不能仅仅将字符串指定为错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
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) }

并使用建议的代码进行测试

1
2
3
4
5
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)

当然,这样做有一个缺点,因为碰巧具有相同错误字符串的不同错误将被评估为相等,这是我们不希望的。


这本书对"保护表示免受意外更新的影响"的解释对我来说似乎具有误导性。不管errorString是结构还是字符串,错误消息仍然是字符串,并且字符串根据规范是不可变的。

这也不是关于唯一性的辩论。例如,尽管两个错误都有完全相同的基础消息,但是errors.New("EOF") == io.EOF的计算结果为false。即使errorString是字符串,这也适用,只要errors.New将返回指向它的指针(请参见我的示例)。

您可以说实现error的结构是惯用的,因为这也是标准库引入自定义错误的方式。看一下encoding/json包中的SyntaxError

1
2
3
4
5
6
type SyntaxError struct {
        Offset int64 // error occurred after reading Offset bytes
        // contains filtered or unexported fields
}

func (e *SyntaxError) Error() string { return e.msg }

(资源)

同样,实现error接口的结构不影响性能,并且不消耗比字符串实现更多的内存。请参阅Go数据结构。


您的testerr程序包运行良好,但失去了"基于结构"标准错误程序包的主要功能:不平等的功能:

1
2
3
4
5
6
7
8
9
10
11
12
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分配一个新的struct,因此与==相比,从New返回的不同值是不同的,尽管错误文本相同。

此处不允许任何编译器产生相同的指针。而"对New的不同调用会产生不同的错误值"这一功能对于防止意外的错误相等很重要。您可以通过使*errorString实现error来修改testerr以产生此属性。尝试一下:您需要一个临时地址。它"感到"错误。可以想象一个花哨的编译器将字符串值内部化,并且可能返回相同的指针(因为它指向相同的内部化字符串),这将破坏此良好的inequality属性。