Making the Invisible Visible: Understanding Go’s Static vs Runtime Types

Coping with Go's Type Assertions drama in static vs runtime scenarios. Read along so you don't have to waste an hour of your life.

Picture this scenario. The Golang compiler shouting at you for using a type that it can iterate over. You have checked the type multiple times but it still won’t compile the code for you. Why is that and where are type assertions fitting in?

Here’s a code example of this. You’re working with JSON data in Go, and you encounter something that seems contradictory.

Wait, what? The type is clearly []interface{} – a slice – so why can’t we iterate over it? The answer reveals a fundamental aspect of Go’s type system.

Root of the Problem: Static vs Runtime Types in Go

The key to understanding this lies in recognizing that Go variables have two types:

  1. Static Type: What the compiler knows at compile time
  2. Runtime Type: What’s actually stored in memory at runtime

Let’s see this in action

Why the Go Compiler Says “No” ??!

When you declare: var result map[string]interface{}

The compiler knows that result["users"] has the static type interface{}.
Even though the JSON unmarshaler puts a []interface{} value there at runtime, the compiler only sees interface{} at compile time.

Understanding the Compiler’s Perspective

I see you’re trying to range over an `interface{}`. But, `interface{}` could be anything – a string, a number, a struct, or yes, even a slice. I can’t generate code to iterate over ‘anything’, so this is a compile error.

Type assertions are your way of saying to the compiler: “Trust me, I know what this really is.”

Complete Type Assertion Chain in JSON Processing

When working with nested JSON structures, you often need multiple type assertions:

Why Each Assertion is Necessary

1. 1st Assertion: Convert interface{} to []interface{} so we can use range

2. 2nd Assertion: Convert each array element from interface{} to map[string]interface{} so we can use bracket notation like user["name"]

3. 3rd Assertion: Convert nested objects from interface{} to map[string]interface{} for the same reason

Alternative Approaches

1. Use Structs for Known Schemas

If you know your JSON structure ahead of time, define structs:

2. Use Libraries for Dynamic JSON

For dynamic JSON structures, consider libraries like gjson

Key Takeaways: Watch out for those Type Assertions

1. Static vs Runtime Types: Go variables have both compile-time and runtime types. How compile-time types actually helps in Go. More compile-time shenanigans from go.

2. Compiler Limitations: The compiler only uses static types to determine valid operations. A discussion.

3. Type Assertions: Change the static type to match the runtime type. When it fails, use https://pkg.go.dev/reflect to find the type.

4. Safety First: Always use the “comma ok” idiom to handle assertion failures.

5. Consider Alternatives: Structs for known schemas, specialized libraries for complex dynamic JSON.

The Mental Model

Think of type assertions as updating the compiler’s knowledge about your data:

  • Before: “This could be anything” (`interface{}`)
  • After: “This is definitely a slice” (`[]interface{}`)

The data itself never changes – only what the compiler allows you to do with it.

Understanding this distinction between static and runtime types is crucial for effective Go programming, especially when working with JSON, reflection, or any situation involving `interface{}`.

Footnote:

This explanation emerged from a real debugging session where the apparent contradiction between seeing `[]interface{}` as the type but being unable to iterate over it caused significant confusion. The key insight is that `%T` shows the runtime type, while the compiler operates on static types.*

That’s it for now, live in the mix.

A worthy bucket to drop in your thoughts, feedback or rant.

This site uses Akismet to reduce spam. Learn how your comment data is processed.