Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is errorString a struct, not a string

Tags:

go

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.

like image 271
shebaw Avatar asked Dec 17 '15 12:12

shebaw


People also ask

What is err != Nil in Golang?

Errors can be returned as nil , and in fact, it's the default, or “zero”, value of on error in Go. This is important since checking if err != nil is the idiomatic way to determine if an error was encountered (replacing the try / catch statements you may be familiar with in other programming languages).

How should you log an error err in Go?

Consider the following code: func main() { if err := doSomething(); err != nil { // log here and exit? } } func doSomething() { f, err := os.

What is the type of error in Go from the system package builtin?

error is a built-in interface type in Go. An error variable represents any value that can describe itself as a string . The interface consists of a single function, Error() , that returns a string error message.

How does Golang handle errors?

Error handling in Golang is done through the built-in interface type, error . It's zero value is nil ; so, if it returns nil , that means that there were no errors in the program.


2 Answers

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.

like image 130
Zippo Avatar answered Sep 17 '22 22:09

Zippo


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 Neware 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.

like image 21
Volker Avatar answered Sep 18 '22 22:09

Volker