-
Couldn't load subscription status.
- Fork 18.4k
Description
What version of Go are you using (go version)?
$ go version go version go1.15.3 linux/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env)?
go env Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/home/username/.cache/go-build" GOENV="/home/username/.config/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/username/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/username/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/lib/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64" GCCGO="gccgo" AR="ar" CC="x86_64-pc-linux-gnu-gcc" CXX="x86_64-pc-linux-gnu-g++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build221298389=/tmp/go-build -gno-record-gcc-switches"
What did you do?
I have a structure that implements the error interface, and an API whose return type is a pointer to that structure type. The API returns nil on success or a pointer to an instance of that structure on failure. I have caller of that API returns error, and returns the value received from the previously described API.
When the caller of the second function examines the error that it returns, the err != nil check always evaluates to true, even when the value is nil.
This example program illustrates the problem
package main
import (
"fmt"
"os"
)
type MyError struct {
msg string
}
func (e *MyError) Error() string {
return e.msg
}
func baz() *MyError {
if len(os.Args) > 1 {
return nil
} else {
return &MyError{msg: "badness"}
}
}
func bar() error {
return baz()
}
func foo() (string, error) {
if err := bar(); err != nil {
return "", err
}
return "hello", nil
}
func main() {
if value, err := foo(); err != nil {
fmt.Printf("error: %s\n", err.Error())
} else {
fmt.Printf("value: %s\n", value)
}
}What did you expect to see?
The sample program above uses the number of command line arguments to control its behavior. If there are no arguments, then baz() returns a pointer to a MyError structure; if there are arguments, then baz() returns nil.
I expect that when the program is run with no arguments, I see the error handled:
$ go run example.go
error: badness
I also expect that when the program is run with arguments, I should see the "no-error" path execute:
$ go run example.go xxx
value: hello
What did you see instead?
When I run the program with arguments --- the case where baz() returns nil --- the err != nil check in main() evaluates to true even though the err is nil, and a call to err.Error() results in e.msg triggering a nil pointer dereference panic:
$ go run example.go xxx
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49a845]
goroutine 1 [running]:
main.(*MyError).Error(0x0, 0xc0000561d0, 0xc000074f48)
/tmp/bar.go:12 +0x5
main.main()
/tmp/bar.go:37 +0x58
exit status 2
I note that in this example, changing the return type of baz() from *MyError to error results in the expected behavior.