gogenerics

Return nil from generic func constrained to interface?


I'm about to migrate existing code to use go generics. This very basic example shows the problem:

// Just an interface
type Object interface {
    Test() error
}

// A func returning an interface can return `nil`
func Get() Object {
    return nil 
}

// Why I can't return `nil` from a generic func constrained to `interface Object`?
func GetG[T Object]() T {
    return nil // ./prog.go:15:9: cannot use nil as T value in return statement
}

https://go.dev/play/p/3WTg1aYDzCA

If func Get, which returns interface Object is allowed to return nil, why is the generic version func GetG where T is constrained to Object not allowed to do so?

Can anyone tell me more about the behaviour or the rational behind it?


Solution

  • A type parameter is not its constraint.

    1. the constraint only restricts the concrete types that can be used to instantiate that type parameter and may determine what operations that type parameter supports
    2. the type parameter exists only at compile time. At run time the code operates with whatever concrete type argument was supplied to the function.
    3. nil may not be assignable to the concrete type argument

    Intuitively, let's say I implement your interface with a defined integer type:

    type Foo int8
    func (Foo) Test() error { return nil }
    

    Does Foo satisfy the constraint Object? Yes, it implements the method. Can I instantiate GetG with Foo? Yes, it satisfies the constraint. Can I assign nil to Foo? No, I can't:

    When storage is allocated for a variable, [...] the variable or value is given a default value. [...] nil for pointers, functions, interfaces, slices, channels, and maps.

    Therefore, you can't always return nil if the return type is a type parameter. You may return nil in other circumstances:

    func GetG[T Object]() *T {
        return nil // ok
    }
    
    // type set comprises only *int and *string
    func GetG[T *int | *string]() T {
        return nil // ok
    }
    

    The case above is mentioned by the language spec in Assignability:

    A value x of type V is assignable to a variable of type T [...] if

    [...]

    • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T's type set.

    With a basic interface (methods only) as a constraint, this condition doesn't hold for the reason explained above: you can satisfy it with types which nil isn't assignable to.

    Otherwise what you can do is to return T's zero value, which you can do in several different ways, the most straightforward one is:

    func GetG[T Object]() T {
        var zero T
        return zero
    }