4.1 var Declarations: Explicit Type and Initializer
Right, so you’ve met the := operator, our cheerful little friend who infers types for us. But sometimes, you need to be more explicit. You need to declare a variable with a specific type, but you’re not quite ready to give it a value yet. Or maybe you are. This is where the var keyword comes in. It’s the more formal, declarative cousin of :=.
The basic syntax is straightforward: you start with var, then the variable name, then the type. If you want to initialize it right away, you tack on = and the value.
var totalCount int // Declare an int named 'totalCount'
var isEnabled = true // Declare and initialize; type inferred as bool
var message string = "Hello" // Explicit type AND initializer (a bit redundant)
Let’s break down the different ways you’ll use this and why you’d choose one over the other.
When You Need a Package-Level Scope
Here’s the first rule, and it’s a big one: Outside of a function, every variable declaration needs to start with var, func, or another keyword. The short declaration operator := is not allowed at the package level. The language designers decided the global scope was no place for such informal shenanigans, and honestly, I agree with them. It forces you to be explicit about what you’re exposing.
package main
// This is perfectly legal and normal at the package level.
var GlobalConfig = loadConfig()
// This is a syntax error. The compiler will yell at you.
// appName := "My Cool App"
func main() {
// But down here in function-land, := is perfectly happy.
appName := "My Cool App"
// ... use appName
}
The Power of the Explicit Zero Value
This is arguably the most important reason to use var. When you declare a variable with a type but no explicit initializer, Go will automatically initialize it to its zero value. This isn’t random garbage data from memory; it’s a sensible, type-specific default. This is a core tenet of Go’s philosophy: variables should be safe to use immediately.
func demonstrateZeroValues() {
var i int // 0
var f float64 // 0.0
var s string // "" (an empty string)
var b bool // false
var slice []int // nil (no underlying array allocated yet)
fmt.Printf("int: %d, float64: %f, string: '%s', bool: %t\n", i, f, s, b)
// Prints: int: 0, float64: 0.000000, string: '', bool: false
// This is safe. It won't panic... yet.
fmt.Println("Slice length:", len(slice)) // Prints: Slice length: 0
// This, however, would cause a runtime panic because the slice is nil.
// fmt.Println(slice[0])
}
Why is this so brilliant? It eliminates a whole class of bugs. You never have to wonder if you “remembered” to initialize something. The act of declaring it is the initialization. It’s the difference between building a house on a prepared foundation versus a random patch of dirt.
The Occasionally Useful Explicit Type and Initializer
You can, of course, do both: declare the type and assign a value. You’ll see this most often when the type you want isn’t the type the value appears to be. My favorite example is handing a float64 literal to a function that expects a float32. Without the explicit type, the compiler will rightfully complain.
func drawText(x, y float32) {
// ... draw some text at the coordinates
}
func main() {
// This doesn't work. 10.5 is an untyped constant, but by default it becomes float64.
// var x = 10.5
// drawText(x, 20.0) // Compile Error: cannot use x (type float64) as type float32
// This works. We explicitly tell the compiler: "This is a float32".
var x float32 = 10.5
drawText(x, 20.0)
// You could also use a conversion, but this is often cleaner.
drawText(float32(10.5), 20.0)
}
The Subtle Art of Grouping Declarations
If you’re declaring a bunch of related variables, especially at the package level, Go lets you group them together in a var block. This makes your code significantly more readable than having a dozen lines all starting with var.
var (
// These are all uninitialized, so they get their zero values.
defaultPort int
defaultHost string
isTLS bool
// And you can mix in initialized ones. The style is consistent.
appVersion = "1.0.0"
buildTime = getBuildTime() // This function runs at init time.
)
The common pitfall here? It’s mostly visual. Don’t create a massive, sprawling block of 50 variables. Group logically related items. If the block gets too long, it’s a sign that your package might be trying to do too much.
So, when do you reach for var instead of :=? Follow this simple rule: Use var when you need the zero value, when you’re at package scope, or when you need to be explicit about the type for the compiler. For everything else inside a function, the brevity of := is usually your best bet.