Why Reflection

Sometimes we need to write a function capable of dealing uniformly with values of types that don’t satisfy a common interface, don’t have a known representation, or don’t exist at the time we design the function—or even all three.

  • type switch 不可能枚举所有的类型,就算可能也不应该这样做,否则会出现使用 type switch 的实现的公共库反倒要依赖调用它的库里定义的类型的情况。

Go provides a mechanism called reflection to update variables and inspect their values at run time, to call their methods, and to apply the operations intrinsic to their representation, all without knowing their types at compile time.

  • fmt, encoding/json, encoding/xml, text/template, html/template, 这些包的实现都依赖于反射。
  • Reflection is provided by the reflect package.

Reflection is complex to reason about and not for casual use. Where possible, you should avoid exposing reflection in the API of a package.

reflect.Type

reflect.Type 表示 Go 的一种类型,它是接口类型,包含许多方法来区分类型、检查组成(如结构体字段、函数参数等),对它的唯一实现就是 type descriptor。

reflect.TypeOf 接收一个 interface{} 类型的参数,返回的是参数的动态类型(具体类型的入参被转成的 interface value),即 reflect.Type 类型。

  • reflect.Type is capable of representing interface types too.
t := reflect.TypeOf(3)
fmt.Println(t.String()) // "int"
fmt.Printf("%T\n", 3)   // "int"
fmt.Println(t)          // "int"
  • 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).
  • reflect.Type 满足 fmt.Stringer 接口,String() 返回的是它的 interface value 动态类型;fmt.Printf("%T") 是这一过程的简写。
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File", not "io.Writer"

Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
func NewFile(fd uintptr, name string) *File
  • Because reflect.TypeOf returns an interface value’s dynamic type, it always returns a concrete type.

reflect.Value

A reflect.Value can hold a value of any type.

reflect.ValueOf 接收一个 interface{} 类型的参数,返回一个包含参数的动态值的 reflect.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)
fmt.Println(v)          // "3"
fmt.Printf("%v\n", v)   // "3"
fmt.Println(v.String()) // "<int Value>"

t := v.Type()
fmt.Println(t.String()) // "int"
  • reflect.Value 满足 fmt.Stringer 接口,但只有 reflect.Value 装的是字符串时会返回具体的值,其它情况都只会返回类型。
  • fmt.Printf("%v") 会打印值,它对 reflect.Values 做了特殊处理。
  • Calling the Type method on a reflect.Value returns its type as a reflect.Type.
v := reflect.ValueOf(3)
x := v.Interface()      // an interface{}
i := x.(int)            // an int
fmt.Printf("%d\n", i)   // "3"
  • 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

reflect.Valueinterface{} 都能容纳任意值,区别是 interface{} 隐藏了值的 representation and intrinsic operations,也不暴露出任何方法,因此只有知道它的动态类型再去断言才能用起来,而 reflect.Value 有很多方法可以用来检查它的内容。

func (v Value) Kind() Kind
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)
  • reflect.ValueKind() 方法可以返回种类。
    • Kind is concerned only with the underlying representation.
  • 类型是无穷的(所以断言行不通),但类型的种类是有限的。
    • 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.

Setting Variables with reflect.Value

Some reflect.Valuess are addressable; others are not.

x := 2 						// value type variable
a := reflect.ValueOf(2) 	// 2 	 int  no
b := reflect.ValueOf(x) 	// 2 	 int  no
c := reflect.ValueOf(&x) 	// &x 	 *int no
d := c.Elem() 				// 2 	 int  yes (x)
  • The value within a is a copy of the integer 2. The same is true of b. The value within c is a copy of the pointer value &x.
  • d, derived from c by dereferencing the pointer within it, refers to a variable and is thus addressable.
  • No reflect.Value returned by reflect.ValueOf(x) is addressable. We can use this approach, calling reflect.ValueOf(&x).Elem(), to obtain an addressable Value for any variable x. We obtain an addressable reflect.Value whenever we indirect through a pointer, even if we started from a non-addressable Value.
    • For example, since the slice indexing expression e[i] implicitly follows a pointer, it is addressable even if the expression e is not. By analogy, reflect.ValueOf(e).Index(i) refers to a variable, and is thus addressable even if reflect.ValueOf(e) is not.
x := 2
d := reflect.ValueOf(&x).Elem() 	// d refers to the variable x
px := d.Addr().Interface().(*int) 	// px := &x
*px = 3 							// x = 3
fmt.Println(x) 						// "3"

d.Set(reflect.ValueOf(4))
fmt.Println(x) 						// "4"

Application

formatAtom

formatAtom treats each value as an indivisible thing with no internal structure.

// https://go.dev/play/p/9uLndi_lrNB

// 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"
	}
}

var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(Any(x))                  // "1"
fmt.Println(Any(d))                  // "1"
fmt.Println(Any([]int64{x}))         // "[]int64 0xc0000b2008"
fmt.Println(Any([]time.Duration{d})) // "[]time.Duration 0xc0000b2020"
  • For aggregate types (structs and arrays) and interfaces it prints only the type of the value.
  • For reference types (channels, functions, pointers, slices, and maps), it prints the type and the reference address in hexadecimal.

Display

Given an arbitrarily complex value x, Display prints the complete structure of that value, labeling each element with the path by which it was found.

func Display(name string, x interface{}) {
	fmt.Printf("Display %s (%T):\n", name, x)
	display(name, reflect.ValueOf(x))
}
func display(path string, v reflect.Value) {
	switch v.Kind() {
	case reflect.Invalid:
		fmt.Printf("%s = invalid\n", path)
	case reflect.Slice, reflect.Array:
		for i := 0; i < v.Len(); i++ {
			display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
		}
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
			display(fieldPath, v.Field(i))
		}
	case reflect.Map:
		for _, key := range v.MapKeys() {
			display(fmt.Sprintf("%s[%s]", path,
				formatAtom(key)), v.MapIndex(key))
		}
	case reflect.Ptr:
		if v.IsNil() {
			fmt.Printf("%s = nil\n", path)
		} else {
			display(fmt.Sprintf("(*%s)", path), v.Elem())
		}
	case reflect.Interface:
		if v.IsNil() {
			fmt.Printf("%s = nil\n", path)
		} else {
			fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
			display(path+".value", v.Elem())
		}
	default: // basic types, channels, funcs
		fmt.Printf("%s = %s\n", path, formatAtom(v))
	}
}

strangelove := Movie{
    Title:    "Dr. Strangelove",
    Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
    Year:     1964,
    Color:    false,
    Actor: map[string]string{
        "Dr. Strangelove":            "Peter Sellers",
        "Grp. Capt. Lionel Mandrake": "Peter Sellers",
        "Pres. Merkin Muffley":       "Peter Sellers",
        "Gen. Buck Turgidson":        "George C. Scott",
        "Brig. Gen. Jack D. Ripper":  "Sterling Hayden",
        `Maj. T.J. "King" Kong`:      "Slim Pickens",
    },
    Oscars: []string{
        "Best Actor (Nomin.)",
        "Best Adapted Screenplay (Nomin.)",
        "Best Director (Nomin.)",
        "Best Picture (Nomin.)",
    },
}

Display("strangelove", strangelove)
// Display strangelove (main.Movie):
// strangelove.Title = "Dr. Strangelove"
// strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
// strangelove.Year = 1964
// strangelove.Color = false
// strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
// strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
// strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
// strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
// strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
// strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
// strangelove.Oscars[0] = "Best Actor (Nomin.)"
// strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
// strangelove.Oscars[2] = "Best Director (Nomin.)"
// strangelove.Oscars[3] = "Best Picture (Nomin.)"
// strangelove.Sequel = nil

Display("os.Stderr", os.Stderr)
// Display os.Stderr (*os.File):
// (*(*os.Stderr).file).pfd.fdmu.state = 0
// (*(*os.Stderr).file).pfd.fdmu.rsema = 0
// (*(*os.Stderr).file).pfd.fdmu.wsema = 0
// (*(*os.Stderr).file).pfd.Sysfd = 2
// (*(*os.Stderr).file).pfd.pd.runtimeCtx = 0
// (*(*os.Stderr).file).pfd.iovecs = nil
// (*(*os.Stderr).file).pfd.csema = 0
// (*(*os.Stderr).file).pfd.isBlocking = 1
// (*(*os.Stderr).file).pfd.IsStream = true
// (*(*os.Stderr).file).pfd.ZeroReadIsEOF = true
// (*(*os.Stderr).file).pfd.isFile = true
// (*(*os.Stderr).file).name = "/dev/stderr"
// (*(*os.Stderr).file).dirinfo = nil
// (*(*os.Stderr).file).nonblock = false
// (*(*os.Stderr).file).stdoutOrErr = true
// (*(*os.Stderr).file).appendMode = false

// the internal representation of the type descriptor for *os.File
Display("rV", reflect.ValueOf(os.Stderr))
// Display rV (reflect.Value):
// (*rV.typ).size = 8
// (*rV.typ).ptrdata = 8
// (*rV.typ).hash = 871609668
// (*rV.typ).tflag = 9
// (*rV.typ).align = 8
// (*rV.typ).fieldAlign = 8
// (*rV.typ).kind = 54
// (*rV.typ).equal = func(unsafe.Pointer, unsafe.Pointer) bool 0x10032e0
// (*(*rV.typ).gcdata) = 1
// (*rV.typ).str = 6509
// (*rV.typ).ptrToThis = 0
// rV.ptr = unsafe.Pointer value
// rV.flag = 22

var i interface{} = 3
Display("i", i)
// Display i (int):
// i = 3
Display("&i", &i)
// Display &i (*interface {}):
// (*&i).type = int
// (*&i).value = 3
  • reflect.Slice, reflect.Array: Len(), Index()
  • reflect.Struct: NumField(), Field()
  • reflect.Ptr: IsNil(), Elem()
  • Cannot handle cycles in the object graph.

References