reflect.Type
and reflect.Value
Reflection is provided by the reflect
package.
It defines two important types, Type
and Value
.
A Type
represents a Go type.
It is an interface with many methods for discriminating among types
and inspecting their components, like the fields of a struct or the
parameters of a function.
The sole implementation of reflect.Type
is the type
descriptor (§7.5), the same entity that identifies the
dynamic type of an interface value.
The reflect.TypeOf
function accepts any interface{}
and returns its dynamic type as a reflect.Type
:
t := reflect.TypeOf(3) // a reflect.Type fmt.Println(t.String()) // "int" fmt.Println(t) // "int"
The TypeOf(3)
call above assigns the value 3
to the
interface{}
parameter.
Recall from Section 7.5 that an assignment from a
concrete value to an interface type performs an implicit interface
conversion, which creates an interface value consisting of two
components: its dynamic type is the operand’s type (int
)
and its dynamic value is the operand’s value (3
).
Because reflect.TypeOf
returns an interface value’s dynamic type,
it always returns a concrete type.
So, for example, the code below prints "*os.File"
, not
"io.Writer"
.
Later, we will see that reflect.Type
is capable of representing
interface types too.
var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w)) // "*os.File"
Notice that reflect.Type
satisfies fmt.Stringer
.
Because printing the dynamic type of an interface value is useful
for debugging and logging, fmt.Printf
provides a shorthand,
%T
, that uses reflect.TypeOf
internally:
fmt.Printf("%T ", 3) // "int"
The other important type in the reflect
package is Value
.
A reflect.Value
can hold a value of any type.
The reflect.ValueOf
function accepts any interface{}
and
returns a reflect.Value
containing the interface’s dynamic
value.
As with reflect.TypeOf
, the results of reflect.ValueOf
are always concrete, but a reflect.Value
can hold interface
values too.
v := reflect.ValueOf(3) // a reflect.Value fmt.Println(v) // "3" fmt.Printf("%v ", v) // "3" fmt.Println(v.String()) // NOTE: "<int Value>"
Like reflect.Type
, reflect.Value
also satisfies
fmt.Stringer
, but unless the Value
holds a string, the
result of the String
method reveals only the type.
Instead, use the fmt
package’s %v
verb, which treats
reflect.Value
s specially.
Calling the Type
method on a Value
returns its type
as a reflect.Type
:
t := v.Type() // a reflect.Type fmt.Println(t.String()) // "int"
The inverse operation to reflect.ValueOf
is the reflect.Value.Interface
method. It returns an interface{}
holding the same concrete
value as the reflect.Value
:
v := reflect.ValueOf(3) // a reflect.Value x := v.Interface() // an interface{} i := x.(int) // an int fmt.Printf("%d ", i) // "3"
A reflect.Value
and an interface{}
can both hold
arbitrary values.
The difference is that an empty interface hides the representation and
intrinsic operations of the value it holds and exposes none of its
methods, so unless we know its dynamic type and use a type assertion
to peer inside it (as we did above), there is little we can do to the
value within.
In contrast, a Value
has many methods for inspecting its
contents, regardless of its type.
Let’s use them for our second attempt at a general formatting
function, which we’ll call format.Any
.
Instead of a type switch, we use reflect.Value
’s
Kind
method to discriminate the cases.
Although there are infinitely many types,
there are only a finite number of kinds of type: the basic types
Bool
, String
, and all the numbers; the aggregate types Array
and
Struct
; the reference types Chan
, Func
, Ptr
,
Slice
, and Map
; Interface
types; and finally Invalid
, meaning no value at all.
(The zero value of a reflect.Value
has kind Invalid
.)
package format import ( "reflect" "strconv" ) // Any formats any value as a string. func Any(value interface{}) string { return formatAtom(reflect.ValueOf(value)) } // formatAtom formats a value without inspecting its internal structure. func formatAtom(v reflect.Value) string { switch v.Kind() { case reflect.Invalid: return "invalid" case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(v.Uint(), 10) // ...floating-point and complex cases omitted for brevity... case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: return strconv.Quote(v.String()) case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map: return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16) default: // reflect.Array, reflect.Struct, reflect.Interface return v.Type().String() + " value" } }
So far, our function treats each value as an indivisible thing with no
internal structure—hence formatAtom
.
For aggregate types (structs and arrays) and interfaces it prints only
the type of the value,
and for reference types (channels, functions, pointers, slices, and maps),
it prints the type and the reference address in hexadecimal.
This is less than ideal but still a major improvement,
and since Kind
is
concerned only with the underlying representation,
format.Any
works for named types too. For example:
var x int64 = 1 var d time.Duration = 1 * time.Nanosecond fmt.Println(format.Any(x)) // "1" fmt.Println(format.Any(d)) // "1" fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0" fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"
44.192.132.66