Skip to content

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

Closed
@adalton

Description

@adalton

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions