algorithmitcus
algorithmitcus

Reputation: 835

Return default value for generic type

How do you return nil for a generic type T?

func (list *mylist[T]) pop() T {
    if list.first != nil {
        data := list.first.data
        list.first = list.first.next
        return data
    }
    return nil
}

func (list *mylist[T]) getfirst() T {
    if list.first != nil {
        return list.first.data
    }
    return nil
}

I get the following compilation error:

 cannot use nil as T value in return statement

Upvotes: 78

Views: 38191

Answers (3)

blackgreen
blackgreen

Reputation: 44665

The *new(T) idiom

This has been suggested as the preferred option in golang-nuts. It is probably less readable but easier to find and replace if/when some zero-value builtin gets added to the language.

It also allows one-line assignments.

The new built-in allocates storage for a variable of any type and returns a pointer to it, so dereferencing *new(T) effectively yields the zero value for T. You can use a type parameter as the argument:

func Zero[T any]() T {
    return *new(T)
}

In case T is comparable, this comes in handy to check if some variable is a zero value:

func IsZero[T comparable](v T) bool {
    return v == *new(T)
}

var of type T

Straightforward and easier to read, though it always requires one line more:

func Zero[T any]() T {
    var zero T
    return zero
}

Named return types

If you don't want to explicitly declare a variable you can use named returns. Not everyone is fond of this syntax, though this might come in handy when your function body is more complex than this contrived example, or if you need to manipulate the value in a defer statement:

func Zero[T any]() (ret T) {
    return
}

func main() {
    fmt.Println(Zero[int]())               // 0
    fmt.Println(Zero[map[string]int]())    // map[]  
    fmt.Println(Zero[chan chan uint64]())  // <nil>
}

It's not a chance that the syntax for named returns closely resembles that of var declarations.

Using your example:

func (list *mylist[T]) pop() (data T) {
    if list.first != nil {
        data = list.first.data
        list.first = list.first.next
    }
    return
}

Return nil for non-nillable types

If you actually want to do this, as stated in your question, you can return *T explicitly.

This can be done when the type param T is constrained to something that excludes pointer types. In that case, you can declare the return type as *T and now you can return nil, which is the zero value of pointer types.

// constraint includes only non-pointer types
func getNilFor[T constraints.Integer]() *T {    
    return nil
}

func main() {
    fmt.Println(reflect.TypeOf(getNilFor[int]()))    // *int
    fmt.Println(reflect.TypeOf(getNilFor[uint64]())) // *uint64
}

Let me state this again: this works best when T is NOT constrained to anything that admits pointer types, otherwise what you get is a pointer-to-pointer type:

// pay attention to this
func zero[T any]() *T {
    return nil
}

func main() {
    fmt.Println(reflect.TypeOf(zero[int]()))  // *int, good
    fmt.Println(reflect.TypeOf(zero[*int]())) // **int, maybe not what you want...
}

Upvotes: 90

Vladislav Popovici
Vladislav Popovici

Reputation: 41

You can init an empty variable.

if l == 0 {
    var empty T
    return empty, errors.New("empty Stack")
}

Upvotes: 2

icza
icza

Reputation: 417592

You can't return nil for any type. If int is used as the type argument for T for example, returning nil makes no sense. nil is also not a valid value for structs.

What you may do–and what makes sense–is return the zero value for the type argument used for T. For example the zero value is nil for pointers, slices, it's the empty string for string and 0 for integer and floating point numbers.

How to return the zero value? Simply declare a variable of type T, and return it:

func getZero[T any]() T {
    var result T
    return result
}

Testing it:

i := getZero[int]()
fmt.Printf("%T %v\n", i, i)

s := getZero[string]()
fmt.Printf("%T %q\n", s, s)

p := getZero[image.Point]()
fmt.Printf("%T %v\n", p, p)

f := getZero[*float64]()
fmt.Printf("%T %v\n", f, f)

Which outputs (try it on the Go Playground):

int 0
string ""
image.Point (0,0)
*float64 <nil>

Upvotes: 101

Related Questions