4.6 Type Inference and When to Add Explicit Types
Right, let’s talk about Go’s party trick: type inference. It’s the language’s way of saying, “I got you, buddy. You don’t need to spell out string every single time; I can see you’re assigning a string literal.” It’s brilliant because it removes a ton of visual noise, letting you focus on the logic, not the ceremony. But like any good party trick, it has its limits, and knowing when to stop relying on it is the mark of a pro.
Go’s := (the short variable declaration operator) is the star here. You use it, the compiler looks at the value on the right, and it deduces the type for the variable on the left. It’s not magic; it’s just good, clean compiler design.
// The compiler sees this...
message := "I'm a string, obviously."
number := 42 // And this is very clearly an int.
// ...and it effectively rewrites it internally as this:
var message string = "I'm a string, obviously."
var number int = 42
When Inference is Your Best Friend
Use this thing everywhere for local variables. I mean it. It makes your code less cluttered and easier to read. Initializing a slice? items := []string{}. Calling a function that returns a value and an error? result, err := someFunction(). This is the idiomatic, standard way to write Go inside a function body. It’s so common that writing out the full var declaration for a simple local variable starts to look a little… formal. Like you’re wearing a tuxedo to a backyard BBQ.
The Limits of the Compiler’s Mind-Reading
The compiler is smart, but it’s not creative. It can only infer a type from the literal value you provide or the return type of the function you’re calling. This leads to its first major limitation: default number types.
What’s the default type for 42? It’s an int. For 3.14? It’s a float64. This is almost always what you want, but sometimes you need to be explicit. Want a float32 instead? The compiler can’t read your mind.
// This gets inferred as a float64 (the default for floating-point literals)
latitude := 39.4743
// If you need a float32, you MUST be explicit. There are two clean ways:
// 1. Use a explicit type conversion on the value
var latitude32 float32 = 39.4743
// 2. Use a typed constant (a bit more rare, but good to know)
const latitudeConst float32 = 39.4743
The same goes for integers. 42 is an int. If you need an int64 for a function parameter, you’ll have to tell it.
The “Zero Value” Paradox and the Need for Explicitness
Here’s the most important rule: You cannot use := without assigning a value. The whole point of := is to infer the type from the value. No value? No inference. This is why you sometimes have to break out the explicit var declaration.
Imagine you need a variable but you won’t have a value for it until later, inside an if block. Using := inside the if would create a new variable scoped to that block (a classic pitfall). The correct way is to declare it with var outside, so it’s available everywhere in the function, and it gets automatically initialized to its zero value.
// WRONG: 'result' is only visible inside the if block.
if success {
result := getTheThing()
}
// fmt.Println(result) // This would be a compile-time error. "result" is undefined here.
// RIGHT: Declare 'result' up here with its explicit type.
var result SomeStruct
if success {
result = getTheThing() // Now we're assigning to the outer variable
}
fmt.Println(result) // Works perfectly.
This is not a failure of type inference; it’s a different tool for a different job. var is for declaring a variable when you don’t have an initial value. := is for when you do.
Clarity Trumps Cleverness Every Time
Finally, there are times when the code is just clearer with an explicit type, even if the compiler could figure it out. This is especially true with complex types like slices of structs or maps.
// This is inferred just fine, but it's a bit dense to read.
// What exactly is being stored here?
config := map[string]map[int][]string{}
// Spelling it out with a type can be a gift to your future self.
type ServerConfig map[int][]string
var config map[string]ServerConfig
You’re not just writing code for the compiler; you’re writing it for the next human who has to read it (who will often be you, at 2 AM, wondering what past-you was thinking). If adding the explicit type makes the code’s intent dramatically clearer, do it. The compiler won’t mind, and your colleagues will thank you. Use inference to remove boilerplate, not to obscure meaning.