How to know if a variable of arbitrary type is Zero in Golang?
Asked Answered
V

4

14

Because not all types are comparable, e.g. a slice. So we can't do this

var v ArbitraryType
v == reflect.Zero(reflect.TypeOf(v)).Interface()
Voodooism answered 14/10, 2015 at 3:7 Comment(0)
P
13

Go 1.13 introduced Value.IsZero method in reflect package. This is how you can check for zero value using it:

if reflect.ValueOf(v).IsZero() {
    // v is zero, do something
}

Apart from basic types, it also works for Chan, Func, Array, Interface, Map, Ptr, Slice, UnsafePointer, and Struct.

Pyromagnetic answered 18/5, 2020 at 19:17 Comment(0)
B
4

Reflect

It depends of what behavior you want when v is an interface (for other types it's the same):

  • reflect.ValueOf(v).IsZero() checks whether the value boxed in the interface is zero
  • reflect.ValueOf(&v).Elem().IsZero() checks whether the interface variable is zero

Demonstration (playground):

var v interface{} = ""
fmt.Println(reflect.ValueOf(v).IsZero())           // true, the empty string "" is zero
fmt.Println(reflect.ValueOf(&v).Elem().IsZero())   // false, the interface itself is not zero

The difference is due to the argument of ValueOf being interface{}. If you pass an interface, it will unbox the dynamic value boxed in it. The Go docs of ValueOf remind that:

ValueOf returns a new Value initialized to the concrete value stored in the interface i

By using ValueOf(&v) instead "the concrete value stored in the interface i" will be a pointer to v. Then you dereference with Elem() to get the original value and finally check IsZero().

Most often than not, what you want is probably reflect.ValueOf(&v).Elem().IsZero(), though YMMV.

Go 1.18 and generics

With generics, you can check if a variable is zero value by using the == operator on a var zero T or *new(T). The type parameter must be comparable (comparable constraint or type set of comparable types).

func IsZeroVar[T ~int64 | ~string](v T) bool {
    var zero T
    return v == zero
}

func IsZeroNew[T ~int64 | ~string](v T) bool {
    return v == *new(T)
}

If the type param is not comparable, you must fall back to reflection, as shown above.

Bottle answered 20/2, 2022 at 20:15 Comment(3)
What would be the benefit of using the generic version instead of reflect, given that it introduces the additional constraint on the type being comparable?Christen
@RyanCollingham if you need to use this function for any type at all, the generic version doesn’t in fact offer benefits. It is an alternative vs. reflection only when the types you plan to compare are or can be restricted to comparable onesBottle
answering my own question here but it seems that the generic version is significantly faster, by a couple orders of magnitude.Christen
V
3

As Peter Noyes points out, you just need to make sure you're not comparing a type which isn't comparable. Luckily, this is very straightforward with the reflect package:

func IsZero(v interface{}) (bool, error) {
    t := reflect.TypeOf(v)
    if !t.Comparable() {
        return false, fmt.Errorf("type is not comparable: %v", t)
    }
    return v == reflect.Zero(t).Interface(), nil
}

See an example use here.

Vachill answered 14/10, 2015 at 4:11 Comment(0)
N
2

Both of the following give me reasonable results (probably because they're the same?)

reflect.ValueOf(v) == reflect.Zero(reflect.TypeOf(v)))

reflect.DeepEqual(reflect.ValueOf(v), reflect.Zero(reflect.TypeOf(v)))

e.g. various integer 0 flavours and uninitialized structs are "zero"

Sadly, empty strings and arrays are not. and nil gives an exception.
You could special case these if you wanted.

Nobe answered 14/10, 2015 at 4:11 Comment(2)
Hi Rhythmic, Thanks for your response! In fact, == and DeepEqual are different. DeepEqual works for both comparable and non comparable variable, according to go document reflect.DeepEqualVoodooism
I'm yet to understand comparable, I'll check it out, thanks.Nobe

© 2022 - 2024 — McMap. All rights reserved.