Unlocking Go’s Type System: From Basic Types to Reflection
This article explains Go’s static type system, introduces custom types and interface types, demonstrates how empty interfaces work, and shows how the reflect package can inspect and modify values at runtime, including struct fields, using concrete code examples.
Types and Interfaces
Go is a statically typed language; each variable has a static type known at compile time, such as int, float32, or a user‑defined type.
<code>type MyInt int
var i int
var j MyInt</code>i has type int, j has type MyInt; they cannot be assigned to each other without conversion.
Interface types are collections of method signatures. Any value that implements those methods can be stored in an interface variable. For example, io.Reader and io.Writer are defined in the io package.
<code>type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}</code>An io.Reader variable can hold any value that implements Read, such as os.Stdin, bufio.NewReader, or a bytes.Buffer. The variable’s static type remains io.Reader.
The empty interface
interface{}has no methods and can hold a value of any type.
Representing Interfaces
Russ Cox’s blog explains that an interface value stores a pair: the concrete value and its type description.
<code>var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)</code>Type assertions can extract the concrete type from an interface value:
<code>var w io.Writer
w = r.(io.Writer)</code>Assigning a concrete value to an empty interface preserves both the value and its full type information.
From Interface to Reflect Value
The reflect package provides
reflect.TypeOfand
reflect.ValueOfto inspect the type and value stored in an interface.
<code>package main
import (
"fmt"
"reflect"
)
func main() {
var f float64 = 13.4
fmt.Println(reflect.TypeOf(f))
fmt.Println("Hello, playground")
}</code>Output shows the type
float64.
reflect.ValueOfreturns a
reflect.Valuethat offers methods such as
Kind,
Int,
Float, and
SetFloat.
<code>var f float64 = 13.44444
v := reflect.ValueOf(f)
fmt.Println(v) // 13.44444...
fmt.Println(v.Type()) // float64
fmt.Println(v.Kind()) // float64
fmt.Println(v.Float()) // 13.44444...</code>Only addressable values are settable;
CanSetreports whether a
reflect.Valuecan be modified. To modify a variable via reflection you must obtain a pointer and call
Elem()to get the underlying settable value.
<code>var x float64 = 3.4
p := reflect.ValueOf(&x) // pointer to x
v := p.Elem() // settable reflect.Value
fmt.Println(v.CanSet()) // true
v.SetFloat(7.1)
fmt.Println(x) // 7.1</code>Modifying Struct Fields
Reflection can also change exported struct fields when you have a pointer to the struct.
<code>type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)</code>The output is
{77 Sunset Strip}. Only exported fields are settable.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.