Search code examples
gogenerics

In Golang, how to compare interface as generics type to nil?


I need a linked node to hold some different interface types, so I made it with generics, but the generics type any can't be compared to nil, it shows error as in the comment:

package main

type myInterface interface {
}
type node[T any] struct {
    next *node[T]
    leaf T
}

func (n *node[T]) GetFirstNodeHasLeaf() *node[T] {
    if n.leaf != nil { // <---- error here: cannot compare n.leaf != nil (mismatched types T and untyped nil)
        return n
    }

    if n.next == nil {
        return nil
    } else {
        return n.next.GetFirstNodeHasLeaf()
    }
}

func main() {
    var n = &node[myInterface]{}
    // fill n with lots of nodes
    n.GetFirstNodeHasLeaf() // get the first node that has (leaf != nil)
}

I also tried to compare with a default value

    var nilT T
    if n.leaf != nilT { // <-- same problem

And restrict the node type as

type node[T myInterface] struct {

Same error, how to solve this? Thanks.


Solution

  • Using an interface to instantiate a generic type like node is probably a conceptual flaw. So let's see the general cases first, and the interface case at the end.

    Using comparable and T

    If you want to use equality operators like == and != with values of type parameter type, the constraint must be comparable.

    type node[T comparable] struct {
        next *node[T]
        leaf T
    }
    

    but then you're not going to test against nil, you would test against T's zero value, which, depending on what you instantiate it with, could be something other than nil.

    In that case you would declare a variable of type T for its zero value:

    var zero T
    if n.leaf != zero {
        return n
    }
    

    However interface types don't implement comparable.

    Using any and *T

    As an alternative, you can keep the constraint any and declare the field leaf as a pointer to T. That supports equality operators, because leaf type isn't a type parameter anymore, it's a pointer:

    type node[T any] struct {
        next *node[T]
        leaf *T
    }
    
    func (n *node[T]) GetFirstNodeHasLeaf() *node[T] {
        if n.leaf != nil { // ok, leaf is a pointer type
            return n
        }
    ...
    }
    

    Using any and T

    With the constraint any, T doesn't support the equality operators; you could instantiate node with literally any type, including those that aren't comparable.

    As long as the field isn't a pointer, you can only use reflection to check for the zero value (nil for pointer types`):

    if !reflect.ValueOf(n.leaf).IsZero() {
        return n
    }
    

    Finally, consider that the above code doesn't work if T is an interface type. What is tested is the dynamic value boxed in the interface. If T really must be an interface, test for the zero value with:

    // leaf is an interface type
    if !reflect.ValueOf(&n.leaf).Elem().IsZero() {
        return n
    }