user187676
user187676

Reputation:

Quick way to detect empty values via reflection in Go

I have a int/string/bool/etc.. value stored in an interface{} and want to determine if it's uninitialized, meaning that it has a value of either

How do I check this?

Upvotes: 39

Views: 35331

Answers (4)

VonC
VonC

Reputation: 1324148

Go 1.13 (Q3 2019) should simplify that detection process:

The new Value.IsZero() method reports whether a Value is the zero value for its type.

It is implemented in src/reflect/value.go, from commit c40bffd and CL 171337, resolving issue 7501

See playground example (as soon as Go 1.13 is supported)

var p *string
v := reflect.ValueOf(p)

fmt.Printf("1.13 v.IsZero()='%v' vs. IsZero(v)='%v'\n", v.IsZero(), IsZero(v))

// Ouput:
// 1.13 v.IsZero()='true' vs. IsZero(v)='true'

However:

v = v.Elem()
fmt.Printf("1.13 v.Elem().IsZero()='%v' vs. IsZero(v.Elem())='%v'\n", v.IsZero(), IsZero(v))

// Panic:
//
// panic: reflect: call of reflect.Value.IsZero on zero Value

As explained in issue 46320:

The problem you are describing is due to the nature of interface types in Go, and to the fact that the reflect package is built on interface types.

A value of interface type always describes some other value.
When you pass a value of interface type to reflect.ValueOf, you get a reflection object describing that other value, not the value of interface type.

To work with interface values with the reflect package you generally need to use pointers.

This doesn't have anything to do with IsZero(), it's just how the reflect package works.

package main

import (
  "fmt"
  "reflect" 
)

func main() {
  var c interface{}

  fmt.Println(reflect.ValueOf(&c).Elem().Kind())
  fmt.Println(reflect.ValueOf(&c).Elem().IsZero())
} 

Basically, one needs to call IsValid() before IsZero()

Upvotes: 27

Geln Yang
Geln Yang

Reputation: 912

@newacct's answer can't detect raw zero value, calling reflect.Value.Interface() on which will cause error. It can use reflect.Value.IsValid() to check that.

// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool 

Update the methods:

func IsZero(v reflect.Value) bool {
    return !v.IsValid() || reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}

func TestIsZero(t *testing.T) {
    var p *string
    v := reflect.ValueOf(p)

    assert.Equal(t, true, v.IsValid())
    assert.True(t, IsZero(v))

    assert.Equal(t, uintptr(0), v.Pointer())

    v = v.Elem()
    assert.Equal(t, false, v.IsValid())
    assert.True(t, IsZero(v))
}

Upvotes: 1

newacct
newacct

Reputation: 122439

From what I understand, you want something like:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}

When talking about interfaces and nil, people always get confused with two very different and unrelated things:

  1. A nil interface value, which is an interface value that doesn't have an underlying value. This is the zero value of an interface type.
  2. A non-nil interface value (i.e. it has an underlying value), but its underlying value is the zero value of its underlying type. e.g. the underlying value is a nil map, nil pointer, or 0 number, etc.

It is my understanding that you are asking about the second thing.


Update: Due to the above code using ==, it won't work for types that are not comparable. I believe that using reflect.DeepEqual() instead will make it work for all types:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

Upvotes: 78

zzzz
zzzz

Reputation: 91243

The zero value* of type interface{} is only nil, not 0 or "" or false.

package main

import "fmt"

func main() {
        var v interface{}
        fmt.Println(v == nil, v == 0, v == "", v == false)
}

(Also http://play.golang.org/p/z1KbX1fOgB)


Output

true false false false

*: [Q]When memory is allocated to store a value, either through a declaration or a call of make or new, and no explicit initialization is provided, the memory is given a default initialization. Each element of such a value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps.[/Q]

Upvotes: 4

Related Questions