MX4399
MX4399

Reputation: 1519

Expecting nil but getting an interface with a nil value in return, which should be nil

Run it at https://play.golang.org/p/sl12vfS9vP

package main

import "fmt"

func main() {
    err := run()
    if err != nil {
        fmt.Printf("%#v", err)
    }
}

func run() (err error) {
    return check()
}

func check() *Result {
    return nil
}

type Result struct {
    message string
}

func (result *Result) Error() string {
    return result.message
}

Upvotes: 1

Views: 3346

Answers (3)

Ainar-G
Ainar-G

Reputation: 36249

This is discussed on the FAQ and the Go Traps website:

An interface value is nil only if the inner value and type are both unset, (nil, nil). In particular, a nil interface will always hold a nil type. If we store a pointer of type *int inside an interface value, the inner type will be *int regardless of the value of the pointer: (*int, nil). Such an interface value will therefore be non-nil even when the pointer inside is nil.

(...)

To return a proper nil error to the caller, the function must return an explicit nil:

func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil
}

Upvotes: 4

MX4399
MX4399

Reputation: 1519

In a nutshell: don't mix interfaces and pointers/values when using direct or named return values in a function.

The goal is to create an error aggregator, e.g:

func checkStatus() *Result {
        r := &Result{}
        // ... do checks and append to r.message ...
        if r.message == "" {
            return nil
        }
        return r
}

func checkSomething() error {
    return runDiagnostics()
}

func runDiagnostics() *Result {
    ... do something
    // all ok 
    return nil
}

.. somewhere else .... (using named return values)
if err = checkStatus(); err != nil {
    return
}

.. this fails even when runDiagnostics() return nil ...
if err = checkSomething(); err != nil {
    ... err is always "something"
}

detailed check with a pointer http://play.golang.org/p/WPGy_ooXmP

detailed check with an interface http://play.golang.org/p/JjWxEj9WRX

solution with an error indicator http://play.golang.org/p/C30s49GiIN

package main

import "fmt"

func main() {
    err := run()
    if err != nil {
        fmt.Printf("%#v", err)
    }
}

func run() (err error) {
    _, err = check()
    return
}

func check() (*Result, error) {
    return nil, nil
}

type Result struct {
    message string
}

func (result *Result) Error() string {
    return result.message
}

solution with an interface http://play.golang.org/p/bFysxTFVIH

package main

import "fmt"

func main() {
    err := run()
    if err != nil {
        fmt.Printf("%#v", err)
    }
}

func run() (err error) {
    return check() // <-- for direct return or named return value an "interface{}" must be used
}

// uses an interface - Result
func check() Result {
    return nil
}

type Result interface {
    Error() string
}

type result struct {
    message string
}

func (result *result) Error() string {
    return result.message
}

Upvotes: 0

thwd
thwd

Reputation: 24848

Francesc Campoy Flores (from the Go team at Google) talked about this particular issue in his presentation at dotGo this year.

You can think an interface value has 2 parts; a type and a value. Therefore an interface of type *Result and value nil is not equal to an interface with both type and value nil.

You can fix your code by typing the nil in the comparison:

http://play.golang.org/p/E9kro7Fkbr

package main

import "fmt"

func main() {
    err := run()
    if err != (*Result)(nil) {
        fmt.Printf("%#v", err)
    }
}

func run() (err error) {
    return check()
}

func check() *Result {
    return nil
}

type Result struct {
    message string
}

func (result *Result) Error() string {
    return result.message
}

Upvotes: 2

Related Questions