Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nil-ness of a pointer is lost when converted to an interface #42306

Closed
adalton opened this issue Oct 30, 2020 · 1 comment
Closed

Nil-ness of a pointer is lost when converted to an interface #42306

adalton opened this issue Oct 30, 2020 · 1 comment

Comments

@adalton
Copy link

adalton commented Oct 30, 2020

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.

@dmitshur
Copy link
Contributor

This is unfortunate but old behavior and working as intended. Please see https://golang.org/doc/faq#nil_error.

If you search the issue tracker for that URL, you'll find many instances of past discussion. Also see #22729 for an open proposal for improving this.

@golang golang locked and limited conversation to collaborators Oct 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants