diff options
Diffstat (limited to 'content/laws-of-reflection.article')
-rw-r--r-- | content/laws-of-reflection.article | 177 |
1 files changed, 89 insertions, 88 deletions
diff --git a/content/laws-of-reflection.article b/content/laws-of-reflection.article index 10cd663..6e34859 100644 --- a/content/laws-of-reflection.article +++ b/content/laws-of-reflection.article @@ -1,10 +1,11 @@ -The Laws of Reflection +# The Laws of Reflection 6 Sep 2011 Tags: interface, reflect, type, technical +Summary: Reflection in computing is the ability of a program to examine its own structure, particularly through types; it's a form of metaprogramming. It's also a great source of confusion. Rob Pike -* Introduction +## Introduction Reflection in computing is the ability of a program to examine its own structure, particularly through types; @@ -16,7 +17,7 @@ Each language's reflection model is different (and many languages don't support but this article is about Go, so for the rest of this article the word "reflection" should be taken to mean "reflection in Go". -* Types and interfaces +## Types and interfaces Because reflection builds on the type system, let's start with a refresher about types in Go. @@ -39,7 +40,7 @@ which represent fixed sets of methods. An interface variable can store any concrete (non-interface) value as long as that value implements the interface's methods. A well-known pair of examples is `io.Reader` and `io.Writer`, -the types `Reader` and `Writer` from the [[https://golang.org/pkg/io/][io package]]: +the types `Reader` and `Writer` from the [io package](https://golang.org/pkg/io/): // Reader is the interface that wraps the basic Read method. type Reader interface { @@ -56,11 +57,11 @@ is said to implement `io.Reader` (or `io.Writer`). For the purposes of this discussion, that means that a variable of type `io.Reader` can hold any value whose type has a `Read` method: - var r io.Reader - r = os.Stdin - r = bufio.NewReader(r) - r = new(bytes.Buffer) - // and so on + var r io.Reader + r = os.Stdin + r = bufio.NewReader(r) + r = new(bytes.Buffer) + // and so on It's important to be clear that whatever concrete value `r` may hold, `r`'s type is always `io.Reader`: @@ -80,9 +81,9 @@ that value will always satisfy the interface. We need to be precise about all this because reflection and interfaces are closely related. -* The representation of an interface +## The representation of an interface -Russ Cox has written a [[https://research.swtch.com/2009/12/go-data-structures-interfaces.html][ detailed blog post]] +Russ Cox has written a [ detailed blog post](https://research.swtch.com/2009/12/go-data-structures-interfaces.html) about the representation of interface values in Go. It's not necessary to repeat the full story here, but a simplified summary is in order. @@ -93,12 +94,12 @@ and that value's type descriptor. To be more precise, the value is the underlying concrete data item that implements the interface and the type describes the full type of that item. For instance, after - var r io.Reader - tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) - if err != nil { - return nil, err - } - r = tty + var r io.Reader + tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return nil, err + } + r = tty `r` contains, schematically, the (value, type) pair, (`tty`, `*os.File`). @@ -107,8 +108,8 @@ even though the interface value provides access only to the `Read` method, the value inside carries all the type information about that value. That's why we can do things like this: - var w io.Writer - w = r.(io.Writer) + var w io.Writer + w = r.(io.Writer) The expression in this assignment is a type assertion; what it asserts is that the item inside `r` also implements `io.Writer`, @@ -120,8 +121,8 @@ even though the concrete value inside may have a larger set of methods. Continuing, we can do this: - var empty interface{} - empty = w + var empty interface{} + empty = w and our empty interface value `empty` will again contain that same pair, (`tty`, `*os.File`). @@ -140,14 +141,14 @@ Interfaces do not hold interface values. Now we're ready to reflect. -* The first law of reflection +## The first law of reflection -* 1. Reflection goes from interface value to reflection object. +## 1. Reflection goes from interface value to reflection object. At the basic level, reflection is just a mechanism to examine the type and value pair stored inside an interface variable. -To get started, there are two types we need to know about in [[https://golang.org/pkg/reflect/][package reflect]]: -[[https://golang.org/pkg/reflect/#Type][Type]] and [[https://golang.org/pkg/reflect/#Value][Value]]. +To get started, there are two types we need to know about in [package reflect](https://golang.org/pkg/reflect/): +[Type](https://golang.org/pkg/reflect/#Type) and [Value](https://golang.org/pkg/reflect/#Value). Those two types give access to the contents of an interface variable, and two simple functions, called `reflect.TypeOf` and `reflect.ValueOf`, retrieve `reflect.Type` and `reflect.Value` pieces out of an interface value. @@ -175,7 +176,7 @@ This program prints You might be wondering where the interface is here, since the program looks like it's passing the `float64` variable `x`, not an interface value, to `reflect.TypeOf`. -But it's there; as [[https://golang.org/pkg/reflect/#TypeOf][godoc reports]], +But it's there; as [godoc reports](https://golang.org/pkg/reflect/#TypeOf), the signature of `reflect.TypeOf` includes an empty interface: // TypeOf returns the reflection Type of the value in the interface{}. @@ -189,8 +190,8 @@ The `reflect.ValueOf` function, of course, recovers the value (from here on we'll elide the boilerplate and focus just on the executable code): - var x float64 = 3.4 - fmt.Println("value:", reflect.ValueOf(x).String()) + var x float64 = 3.4 + fmt.Println("value:", reflect.ValueOf(x).String()) prints @@ -210,11 +211,11 @@ a constant indicating what sort of item is stored: Also methods on `Value` with names like `Int` and `Float` let us grab values (as `int64` and `float64`) stored inside: - var x float64 = 3.4 - v := reflect.ValueOf(x) - fmt.Println("type:", v.Type()) - fmt.Println("kind is float64:", v.Kind() == reflect.Float64) - fmt.Println("value:", v.Float()) + var x float64 = 3.4 + v := reflect.ValueOf(x) + fmt.Println("type:", v.Type()) + fmt.Println("kind is float64:", v.Kind() == reflect.Float64) + fmt.Println("value:", v.Float()) prints @@ -234,29 +235,29 @@ That is, the `Int` method of `Value` returns an `int64` and the `SetInt` value takes an `int64`; it may be necessary to convert to the actual type involved: - var x uint8 = 'x' - v := reflect.ValueOf(x) - fmt.Println("type:", v.Type()) // uint8. - fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. - x = uint8(v.Uint()) // v.Uint returns a uint64. + var x uint8 = 'x' + v := reflect.ValueOf(x) + fmt.Println("type:", v.Type()) // uint8. + fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true. + x = uint8(v.Uint()) // v.Uint returns a uint64. The second property is that the `Kind` of a reflection object describes the underlying type, not the static type. If a reflection object contains a value of a user-defined integer type, as in - type MyInt int - var x MyInt = 7 - v := reflect.ValueOf(x) + type MyInt int + var x MyInt = 7 + v := reflect.ValueOf(x) the `Kind` of `v` is still `reflect.Int`, even though the static type of `x` is `MyInt`, not `int`. In other words, the `Kind` cannot discriminate an int from a `MyInt` even though the `Type` can. -* The second law of reflection +## The second law of reflection -* 2. Reflection goes from reflection object to interface value. +## 2. Reflection goes from reflection object to interface value. Like physical reflection, reflection in Go generates its own inverse. @@ -269,8 +270,8 @@ representation and returns the result: As a consequence we can say - y := v.Interface().(float64) // y will have type float64. - fmt.Println(y) + y := v.Interface().(float64) // y will have type float64. + fmt.Println(y) to print the `float64` value represented by the reflection object `v`. @@ -281,13 +282,13 @@ been doing in the previous examples. Therefore all it takes to print the contents of a `reflect.Value` correctly is to pass the result of the `Interface` method to the formatted print routine: - fmt.Println(v.Interface()) + fmt.Println(v.Interface()) (Why not `fmt.Println(v)`? Because `v` is a `reflect.Value`; we want the concrete value it holds.) Since our value is a `float64`, we can even use a floating-point format if we want: - fmt.Printf("value is %7.1e\n", v.Interface()) + fmt.Printf("value is %7.1e\n", v.Interface()) and get in this case @@ -302,17 +303,17 @@ except that its result is always of static type `interface{}`. Reiterating: Reflection goes from interface values to reflection objects and back again. -* The third law of reflection +## The third law of reflection -* 3. To modify a reflection object, the value must be settable. +## 3. To modify a reflection object, the value must be settable. The third law is the most subtle and confusing, but it's easy enough to understand if we start from first principles. Here is some code that does not work, but is worth studying. - var x float64 = 3.4 - v := reflect.ValueOf(x) - v.SetFloat(7.1) // Error: will panic. + var x float64 = 3.4 + v := reflect.ValueOf(x) + v.SetFloat(7.1) // Error: will panic. If you run this code, it will panic with the cryptic message @@ -325,9 +326,9 @@ and not all reflection `Values` have it. The `CanSet` method of `Value` reports the settability of a `Value`; in our case, - var x float64 = 3.4 - v := reflect.ValueOf(x) - fmt.Println("settability of v:", v.CanSet()) + var x float64 = 3.4 + v := reflect.ValueOf(x) + fmt.Println("settability of v:", v.CanSet()) prints @@ -340,15 +341,15 @@ It's the property that a reflection object can modify the actual storage that was used to create the reflection object. Settability is determined by whether the reflection object holds the original item. When we say - var x float64 = 3.4 - v := reflect.ValueOf(x) + var x float64 = 3.4 + v := reflect.ValueOf(x) we pass a copy of `x` to `reflect.ValueOf`, so the interface value created as the argument to `reflect.ValueOf` is a copy of `x`, not `x` itself. Thus, if the statement - v.SetFloat(7.1) + v.SetFloat(7.1) were allowed to succeed, it would not update `x`, even though `v` looks like it was created from `x`. @@ -375,10 +376,10 @@ a pointer to the value we want to modify. Let's do that. First we initialize `x` as usual and then create a reflection value that points to it, called `p`. - var x float64 = 3.4 - p := reflect.ValueOf(&x) // Note: take the address of x. - fmt.Println("type of p:", p.Type()) - fmt.Println("settability of p:", p.CanSet()) + var x float64 = 3.4 + p := reflect.ValueOf(&x) // Note: take the address of x. + fmt.Println("type of p:", p.Type()) + fmt.Println("settability of p:", p.CanSet()) The output so far is @@ -390,8 +391,8 @@ but it's not `p` we want to set, it's (in effect) `*p`. To get to what `p` points to, we call the `Elem` method of `Value`, which indirects through the pointer, and save the result in a reflection `Value` called `v`: - v := p.Elem() - fmt.Println("settability of v:", v.CanSet()) + v := p.Elem() + fmt.Println("settability of v:", v.CanSet()) Now `v` is a settable reflection object, as the output demonstrates, @@ -399,9 +400,9 @@ Now `v` is a settable reflection object, as the output demonstrates, and since it represents `x`, we are finally able to use `v.SetFloat` to modify the value of `x`: - v.SetFloat(7.1) - fmt.Println(v.Interface()) - fmt.Println(x) + v.SetFloat(7.1) + fmt.Println(v.Interface()) + fmt.Println(x) The output, as expected, is @@ -413,7 +414,7 @@ albeit through reflection `Types` and `Values` that can disguise what's going on Just keep in mind that reflection Values need the address of something in order to modify what they represent. -* Structs +## Structs In our previous example `v` wasn't a pointer itself, it was just derived from one. @@ -426,22 +427,22 @@ Here's a simple example that analyzes a struct value, `t`. We create the reflection object with the address of the struct because we'll want to modify it later. Then we set `typeOfT` to its type and iterate over the fields using straightforward -method calls (see [[https://golang.org/pkg/reflect/][package reflect]] for details). +method calls (see [package reflect](https://golang.org/pkg/reflect/) for details). Note that we extract the names of the fields from the struct type, but the fields themselves are regular `reflect.Value` objects. - type T struct { - A int - B string - } - t := T{23, "skidoo"} - s := reflect.ValueOf(&t).Elem() - typeOfT := s.Type() - for i := 0; i < s.NumField(); i++ { - f := s.Field(i) - fmt.Printf("%d: %s %s = %v\n", i, - typeOfT.Field(i).Name, f.Type(), f.Interface()) - } + type T struct { + A int + B string + } + t := T{23, "skidoo"} + s := reflect.ValueOf(&t).Elem() + typeOfT := s.Type() + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + fmt.Printf("%d: %s %s = %v\n", i, + typeOfT.Field(i).Name, f.Type(), f.Interface()) + } The output of this program is @@ -454,9 +455,9 @@ of a struct are settable. Because `s` contains a settable reflection object, we can modify the fields of the structure. - s.Field(0).SetInt(77) - s.Field(1).SetString("Sunset Strip") - fmt.Println("t is now", t) + s.Field(0).SetInt(77) + s.Field(1).SetString("Sunset Strip") + fmt.Println("t is now", t) And here's the result: @@ -466,15 +467,15 @@ If we modified the program so that `s` was created from `t`, not `&t`, the calls to `SetInt` and `SetString` would fail as the fields of `t` would not be settable. -* Conclusion +## Conclusion Here again are the laws of reflection: -- Reflection goes from interface value to reflection object. + - Reflection goes from interface value to reflection object. -- Reflection goes from reflection object to interface value. + - Reflection goes from reflection object to interface value. -- To modify a reflection object, the value must be settable. + - To modify a reflection object, the value must be settable. Once you understand these laws reflection in Go becomes much easier to use, although it remains subtle. |