e.nikolov
e.nikolov

Reputation: 137

How to check if the value of a generic type is the zero value?

The generics proposal has a section on how to return the zero value of a type parameter, but it does not mention how to check if a value of a parametrized type is the zero value.

The only way I can think of is to use reflect.ValueOf(...).IsZero(), is there an alternative?

Upvotes: 6

Views: 5577

Answers (2)

user12258482
user12258482

Reputation:

If the type is comparable, then compare the value to a variable set to the zero value of the type.

var zero T
isZero := v == zero

Use the reflect package if the type is not comparable:

isZero := reflect.ValueOf(&v).Elem().IsZero()

Upvotes: 3

blackgreen
blackgreen

Reputation: 44665

Use the equal operator with the *new(T) idiom. The constraint of the generic param must be, or embed, comparable to support the equality operator, or specify a type set of comparable types:

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

If you can’t constrain T to comparable then you’re left with reflection. Zombo’s suggestion to address the variable like this:

reflect.ValueOf(&v).Elem().IsZero()

is superior to simply reflect.ValueOf(v).IsZero() because ValueOf takes an interface{} argument and if v just happens to be an interface you would loose that information. I don’t think that with generics you will often instantiate a function with an interface but it’s worth knowing. So:

    func IsZero[T any](v T) bool {
        return reflect.ValueOf(&v).Elem().IsZero()
    }

Using a variable of type T and using that in the comparison also works. It is more readable than *new(T), but it requires an additional var declaration. The operands still must be comparable:

func IsZero[T comparable](v T) bool {
    var zero T
    return v == zero
}

If you are concerned about the performance of reflection, it is indeed slightly worse than pure comparable generics:

go1.18rc1 test -v -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: example.com
BenchmarkGenerics-10        1000000000      0.3295 ns/op     0 B/op   0 allocs/op
BenchmarkReflection-10      94129023        12.26 ns/op      8 B/op   1 allocs/op

If you know you won't instantiate the reflect-based function with an interface, you can simplify the code to reflect.ValueOf(v).IsZero() which gives a minor improvement:

go1.18rc1 test -v -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: example.com
BenchmarkGenerics-10          1000000000    0.3295 ns/op     0 B/op   0 allocs/op
BenchmarkReflection-10        94129023      12.26 ns/op      8 B/op   1 allocs/op
BenchmarkReflectionNoAddr-10  100000000     11.41 ns/op      8 B/op   0 allocs/op

Upvotes: 6

Related Questions