Mariy
Mariy

Reputation: 5924

Structs cannot be nil, but this is compiling

I'm playing with Go Playground and find this code:

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

So here I can see that *MyError implements error interface. However if I remove & in error func and return MyError instead, I receive compile time error: prog.go:19: cannot use MyError literal (type MyError) as type error in return argument: MyError does not implement error (Error method has pointer receiver). OK I can understand that, so I can make the function Error like this and this will compile and run successfully:

func (e MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return MyError{
        time.Now(),
        "it didn't work",
    }
}

Then I see in main func that there is check if err is nil so if I understand correctly it is perfectly possible func error to return nil in some situations. So it is possible for MyError struct to take nil values. But then if I try to compile this:

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return nil
    return MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    var err2 MyError = nil

    fmt.Println(err2)
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

go compiler said that: prog.go:27: cannot use nil as type MyError in assignment [process exited with non-zero status]

Why in the upper case the compilation is successful and in this case the compilation fails? Is it possible for structs to be nil (I guess not, but then why run func compiles?)

Upvotes: 2

Views: 12708

Answers (2)

Paul Hankin
Paul Hankin

Reputation: 58409

A nil interface is not the same as an interface containing a nil value.

In your question, you have seen that an interface (in your case, the builtin error interface) can be nil, and drawn an incorrect conclusion that every type that can implement the interface can also be nil.

Upvotes: 3

julienc
julienc

Reputation: 20355

In the first example, *MyError implemented the error interface. As you can see, it's a pointer, and a pointer can have the nil value.

var err *MyError
err == nil // true

var err *MyError = new(MyError)
err == nil // false

But in the second example, it is MyError which implements the error interface, and that is not a pointer any more.

var err MyError
err == MyError{} // true
&err == nil // false
err == nil // Compilation error

It is the adress of err this time that can be nil, not the variable itself.


As a comparison, consider the int type: var i int. You can check i == 0 for instance, but it would be an error to test if i == nil, because nil is not an integer (exactly as it was not a MyError before). But you could still check if the address of i is nil: &i == nil.


EDIT

Be aware that this function will always return nil (the execution stops right after the first return):

func run() error {
    return nil
    return MyError{
        time.Now(),
        "it didn't work",
    }
}

It compiles because the prototype of the function tells that it must return an error, and indeed, nil is a valid error, and so is a MyError{} variable. But try to change this function prototype to this one:

func run() MyError

You will see that the compilation fails, because nil is not a MyError variable, even though it is a valid value for the error type.

Upvotes: 11

Related Questions