Golang reflect and golang reflection: Type, Value, Kind, Set, MakeFunc, StructOf

Golang reflection with reflect.TypeOf and reflect.ValueOf, Kind vs Type, struct fields and tags, settable values and Set, reflect.New, MakeSlice MakeMap MakeChan, MakeFunc wrappers, StructOf limits, Value.Call, reflect.Append, DeepEqual and IsZero, laws of reflection, vs generics.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

Golang reflect and golang reflection: Type, Value, Kind, Set, MakeFunc, StructOf

Golang reflect is the standard reflect package. People also say golang reflection, reflect golang, go reflection, or goreflect when they mean the same thing: inspect or manipulate types and values at run time, usually starting from an any / interface{}. Go's model is summarized in The Laws of Reflection: reflection moves between interface values and reflect.Type / reflect.Value, and only settable Values can be written through. This guide goes deeper than a short "TypeOf and walk a struct" article: it covers Elem, Set, New, MakeSlice / MakeMap / MakeChan, MakeFunc, StructOf, Call, Append, DeepEqual, limits (no dynamic interface implementation), and when generics replace reflection. For lighter type printing without walking fields, see print type in Go; for interfaces without reflect, see interfaces in Go and type assertions.

Checked with Go 1.24 on 64-bit Linux. Long snippets use {run=false} so you can paste them locally; short programs match a single main.go layout.


Laws of reflection and the three objects

Rob Pike's The Laws of Reflection is still the best mental model:

  1. Reflection starts from an interface value (including any) and produces reflection objects (Type, Value).
  2. You can turn a Value back into any with Interface(), losing static type information until you assert or switch.
  3. To mutate through Value, the value must be settable (CanSet): almost always pass a pointer to ValueOf, then Elem() to reach the struct or scalar inside.

reflect.TypeOf describes layout and names; reflect.ValueOf holds the concrete bits. Kind is a coarse enum (struct, ptr, slice, …). Two different named struct types have different Type values but the same Kind (struct).


Type vs Kind, Elem, and struct metadata

Name, Kind, and Elem

Type.Name is empty for anonymous or composite tops (for example the type of []int has no name). For a defined type Foo, Name is Foo and Kind is struct. For pointers, slices, maps, channels, and arrays, Elem() returns the element type.

Struct fields and tags

On a Kind struct, NumField and Field(i) return StructField values with Name, Type, Index, Anonymous, and Tag—what encoding/json reads for json:"...". Unexported fields are visible to reflection in the same package only; from another package you cannot take Interface() on them.

Panics are normal if you skip Kind checks

Many Type and Value methods panic when the Kind is wrong (for example Field on a map). Guard with switch v.Kind() { ... } before deep logic.

go
package main

import (
	"fmt"
	"reflect"
)

type Foo struct {
	A int    `tag1:"First Tag" tag2:"Second Tag"`
	B string
}

func dumpType(t reflect.Type, indent string) {
	fmt.Printf("%sname=%q kind=%s\n", indent, t.Name(), t.Kind())
	switch t.Kind() {
	case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Array, reflect.Chan:
		dumpType(t.Elem(), indent+"  ")
	case reflect.Struct:
		for i := 0; i < t.NumField(); i++ {
			sf := t.Field(i)
			fmt.Printf("%sfield %d: %s type=%s kind=%s tag1=%q\n",
				indent, i+1, sf.Name, sf.Type, sf.Type.Kind(), sf.Tag.Get("tag1"))
		}
	}
}

func main() {
	var xs []int
	dumpType(reflect.TypeOf(xs), "")
	dumpType(reflect.TypeOf(Foo{}), "")
	dumpType(reflect.TypeOf((*Foo)(nil)), "")
}

One local run began like:

text
name="" kind=slice
  name="int" kind=int
name="Foo" kind=struct
field 1: A type=int kind=int tag1="First Tag"
field 2: B type=string kind=string tag1=""
name="" kind=ptr
  name="Foo" kind=struct
  field 1: A type=int kind=int tag1="First Tag"
  ...

reflect.Value: read, write, New

Read-only vs settable

reflect.ValueOf(x) on a value copy is usually not settable. For a struct field you need reflect.ValueOf(&s).Elem().Field(i) and verify CanSet before Set, SetInt, SetString, etc. Wrong Kind plus SetInt panics.

reflect.New(t) allocates a pointer to zero value of type t and returns a Value holding *T; combine with Elem() to mutate the inner value.

go
package main

import (
	"fmt"
	"reflect"
)

type S struct {
	A int
	B string
}

func main() {
	x := S{A: 1, B: "hello"}
	v := reflect.ValueOf(&x).Elem()
	v.Field(0).SetInt(20)
	v.Field(1).SetString("goodbye")
	fmt.Println(x)

	n := reflect.New(reflect.TypeOf(0)).Elem()
	n.SetInt(99)
	fmt.Println(n.Interface().(int))
}

Expected lines include {20 goodbye} and 99.

Value.String vs fmt for numbers

Value.String: for Kind == String it returns the string; for other kinds it returns a descriptive placeholder, not a decimal rendering of an int. Use Int, Uint, Float, or format v.Interface().


MakeSlice, MakeMap, and MakeChan

Where the language uses make, reflection exposes reflect.MakeSlice, reflect.MakeMap, and reflect.MakeChan. You pass a reflect.Type (often from reflect.TypeOf((*T)(nil)).Elem() or TypeOf(zero) tricks) and receive a Value you can fill and finally .Interface() into a typed variable.

go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	sl := reflect.MakeSlice(reflect.TypeOf([]int(nil)), 1, 3)
	sl.Index(0).SetInt(10)

	mp := reflect.MakeMap(reflect.TypeOf(map[string]int(nil)))
	mp.SetMapIndex(reflect.ValueOf("hello"), reflect.ValueOf(10))

	fmt.Println(sl.Interface(), mp.Interface())
}

Expected shape: [10] map[hello:10] (map iteration order not guaranteed elsewhere).


reflect.MakeFunc: generated wrappers

reflect.MakeFunc builds a func value from a reflect.Type whose Kind is Func and a shim func([]reflect.Value) []reflect.Value. Common uses include decorators (timing, tracing), RPC adapters, and small DSLs. The in/out Value slices must match the function type exactly.

go
package main

import (
	"fmt"
	"reflect"
	"time"
)

func work() {
	time.Sleep(100 * time.Millisecond)
	fmt.Print("starting")
	time.Sleep(50 * time.Millisecond)
	fmt.Println("ending")
}

func wrap(fn interface{}) interface{} {
	v := reflect.ValueOf(fn)
	return reflect.MakeFunc(v.Type(), func(args []reflect.Value) []reflect.Value {
		t0 := time.Now()
		out := v.Call(args)
		fmt.Printf("calling took %s\n", time.Since(t0))
		return out
	}).Interface()
}

func main() {
	timed := wrap(work).(func())
	timed()
}

You should see startingending then a calling took ... line.


StructOf: anonymous struct types and sharp edges

reflect.StructOf builds a new struct type from a slice of StructField descriptions. The result is an unnamed struct type; you mostly work through reflect.Value after reflect.New. This is niche compared to normal type T struct { ... }, but it appears in dynamic schemas and some serializers.

Limitations worth knowing (also discussed in older public tutorials on golang reflection):

  • Types built with StructOf cannot gain methods, so they cannot implement interfaces at runtime the way a hand-written type User struct{} with methods does.
  • Embedding and delegation interact badly with some reflective constructions; see golang/go#15924 and golang/go#16522 for historical context—treat StructOf as advanced and test-heavy.
go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	fields := []reflect.StructField{
		{Name: "X", Type: reflect.TypeOf(0), Tag: `json:"x"`},
		{Name: "Y", Type: reflect.TypeOf([]int{}), Tag: ""},
	}
	st := reflect.StructOf(fields)
	v := reflect.New(st).Elem()
	v.Field(0).SetInt(2)
	v.Field(1).Set(reflect.ValueOf([]int{1, 2, 3}))
	fmt.Println(v.Interface())
	fmt.Println(st.String())
}

Value.Call, Method, and MethodByName

Value.Call invokes a func Value. Method and MethodByName are easy to misuse across pointer vs value receivers; many callers use reflect.ValueOf(&x) then Elem() or Addr() deliberately.

go
package main

import (
	"fmt"
	"reflect"
)

func Add(a, b int) int { return a + b }

func main() {
	v := reflect.ValueOf(Add)
	out := v.Call([]reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)})
	fmt.Println(out[0].Int())
}

5 is the expected result.


Slices: Index, Set, Len, Cap, Append

Use Len, Cap, Index for elements. Growing slices goes through reflect.Append, then assign back into the original Value if you started from an addressable slice.

go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := []int{1}
	v := reflect.ValueOf(&s).Elem()
	v.Set(reflect.Append(v, reflect.ValueOf(2)))
	fmt.Println(s)
}

Expected: [1 2].


DeepEqual, IsZero, and Comparable

reflect.DeepEqual is convenient for tests but a poor default for domain equality: it distinguishes nil and empty slices in ways that surprise newcomers, recurses through interfaces, and ignores your business rules.

Value.IsZero matches the language's "all fields zero" rule for structs and is useful for validation frameworks.

For ordered comparison of arbitrary values in tests, many teams prefer github.com/google/go-cmp/cmp over DeepEqual.


Generics vs golang reflect

Since Go 1.18, generics cover many patterns that older code solved with reflect (containers, parsers with type parameters). Rule of thumb:

  • Generics when the type parameter is known at compile time for each instantiation (tree, result wrapper, parser for T).
  • Reflection when types are data (JSON into unknown shapes, SQL row scanners, plugin architectures, struct tag-driven routers).

You can combine both: generic APIs with small reflect cores.


Safety checklist before you ship reflection

  • Branch on Kind before calling type-specific APIs.
  • Check CanSet, CanAddr, and Nil() where relevant.
  • Avoid MethodByName on hot paths; it allocates and string-compares.
  • Remember JSON and gob already embed reflection; sometimes the right move is configuration, not a new reflective layer.
  • Prefer compile-time helpers (generics, code generation) when the type set is closed.

Summary

Golang reflection spans reflect.TypeOf, reflect.ValueOf, Kind, struct fields and tags, settable paths through pointers, allocation with New, container construction with MakeSlice / MakeMap / MakeChan, higher-order shims with MakeFunc, anonymous struct types via StructOf, calls through Call, growth with Append, and test utilities like DeepEqual / IsZero. It cannot invent methods or implement interfaces dynamically the way some JVM metaprogramming can. For golang reflection work in production, keep the Laws of Reflection in mind, guard Kinds, and reach for generics first when they express the same design more clearly.


References


Frequently Asked Questions

1. What is golang reflection vs static typing?

Reflection inspects or changes values whose types are not fixed in your source at compile time, using reflect.Type and reflect.Value; static typing still applies at compile time, but you opt into runtime checks and possible panics inside reflect.

2. What does reflect.TypeOf return for a nil interface value?

It returns nil reflect.Type because there is no dynamic type to describe; guard with if t == nil before using t.

3. Why does reflect.Value String show angle brackets for an int?

Value.String only returns the underlying string for Kind String; for other kinds it returns a placeholder like ; use Int, fmt.Sprint on Interface(), or strconv for numeric text.

4. When does Field or NumField panic?

They require Kind struct; Field panics on out-of-range index; Interface on unexported fields panics when called from another package.

5. How do I modify a value with reflection?

Use reflect.ValueOf(&x).Elem() so the Value is settable, then Field(i).Set or SetInt after checking Kind; reflect.ValueOf(x) on a value copy is usually not settable.

6. Can reflection create new methods or implement an interface at runtime?

No; you can build functions with MakeFunc and anonymous struct types with StructOf, but you cannot attach new named methods to satisfy interfaces dynamically.

7. When should I prefer generics over golang reflect?

Prefer generics or type parameters when the type set is known at compile time; use reflection for encoders, scanners, DI containers, and tests where types are data-driven.

8. What is wrong with reflect.DeepEqual for business logic?

It treats nil slices and empty slices as unequal in some cases and recurses arbitrarily; prefer typed equality functions or cmp.Equal with go-cmp for stable semantics.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …