Go version

go version go1.22.6 darwin/arm64

Output of go env in your module/workspace:

-

What did you do?

Please see https://go.dev/play/p/gOQFnwKeKIV

The crux being:

func F1[T newableObject[U], U any](obj *T) {
    fmt.Println("Inside F1", *obj, *obj == nil)

    F2[T, U](*obj) // *obj == nil per the above, gets un-nilled in the child
    F2[T, U](nil)  // Passing nil directly retains the nil-ness
}

func F2[T newableObject[U], U any](obj object) {
    fmt.Println("Inside F2", obj, obj == nil)
}

func main() {
    var obj *S // nil
    F1(&obj)
}
Inside F1 <nil> true
Inside F2 <nil> false
Inside F2 <nil> true

In essence, inside a generic method, a pointer to an interface can contain nil interface, but when passing that nil interface to another generics method, it gets turned into a non-nil interface containing nil. This is bad because the user's nil input gets turned into a non-nil-nil, for which there isn't even a cheap way to test against apart from using reflection.

What did you see happen?

A a nil interface gets turned into a non-nil-interface-containing-nil.

What did you expect to see?

I expect a nil-interface to retain it's nil-ness.

Comment From: gabyhelp

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: karalabe

FWIW, seems if I change the type of F2's obj from object to T it works correctly. This was a fault with me refactoring a previous code and keeping the interface type instead of using the generic one, but still, I find the behavior odd and would venture to say it's buggy?

Comment From: zephyrtronium

This is https://go.dev/doc/faq#nil_error. You have a non-nil interface containing nil. It may be surprising, but it is following the rules of the language.

Comment From: karalabe

@zephyrtronium The point is that the generics code converts a nil interface to a non-nil interface, whereas it shouldn't do such a thing. That's what the issue is about.

Comment From: randall77

I agree with @zephyrtronium, this is just how putting nil pointers in interfaces work. There is nothing that is converting a nil interface to a non-nil interface. There is something converting a variable whose type is a type parameter constrained by an interface, but that's not the same thing.

To be explicit about instantiations, the call to F1 is F1[*S, S](&obj). Inside that instantiation of F1, T and U are not interface types.