4.2 Short Variable Declaration :=
Right, let’s talk about the workhorse of variable creation in Go: the short variable declaration. You’re going to see := more than you see your own family, so we’d better get to know it well. It’s not magic, it’s just wonderfully concise syntax sugar for declaring and initializing a variable in one fell swoop. The key thing to remember is the colon (:). It’s the declaration part. The equals sign (=) is the assignment part. Put them together, and you’ve told Go, “Hey, make a new variable with this value, and figure out the type for me, will you?”
Here’s the basic idea. Instead of the verbose:
var count int = 10
You simply write:
count := 10
The compiler sees 10, an untyped integer constant, and says, “Right, count is an int.” It handles the type declaration for you. This is type inference in action, and it’s brilliant for keeping code clean and readable.
The Rules of Engagement
You can’t just throw := around willy-nilly. It has rules, and they’re actually sensible.
- It must declare at least one new variable. This is the core rule. You use
:=when you’re introducing new variables into the current scope. You can mix and match, declaring some new ones and reassigning others, which is incredibly handy.name, age, isAdmin := "Dante", 35, true // All new declarations. Fine. // Later, in the same scope... age, location := 36, "Malbolge" // 'age' is reassigned, 'location' is new. This is also fine. - It only works inside function bodies. Try using
:=at the package level and the compiler will slap your wrist. For those global variables, you must use a fullvardeclaration. This keeps global state explicit, which is a good thing. - It requires an initial value. This should be obvious. The whole point is for the compiler to infer the type from the value you provide. No value, no type. No type, no variable. It’s a package deal.
Why You’ll Love It (And One Reason You Might Not)
The beauty of := is its sheer convenience. It reduces visual clutter immensely. It’s perfect for getting values from function returns:
f, err := os.Open("filename.txt")
if err != nil {
log.Fatal(err)
}
// Use the file 'f'
Boom. You’ve declared f and err, handled the error check, and moved on. Clean, clear, and efficient.
Now, the one tiny gotcha: variable shadowing. This is the biggie. If you’re not careful, you can accidentally create a new variable in an inner scope when you meant to assign to an existing one. The compiler will usually let you do this, because you are declaring a new variable (it’s new to that inner block), but it can lead to incredibly confusing bugs.
var count int = 10 // Outer scope 'count'
if true {
count := 50 // Inner scope 'count' SHADOWS the outer one!
fmt.Println("Inner count:", count) // 50
}
fmt.Println("Outer count:", count) // 10 ...wait, what?
See? The outer count remains blissfully unchanged. This happens most often with err in if blocks. Always check that your := is doing what you think it’s doing. If you get weird values, shadowing is the first place to look.
Best Practices from the Trenches
- Use it for obvious initializations. When the type is crystal clear from the right-hand side (
i := 0,s := "hello",p := &User{}),:=is your best friend. - Be cautious with non-obvious types. For things like
byteorrune, where the default type might not be what you want, sometimes a explicitvardeclaration is clearer:var letter byte = 'A'. - Embrace the multi-declaration. It’s fantastic for getting multiple return values, as we saw with
os.Open. - Watch your scope like a hawk. Seriously, the shadowing thing will bite you. Use tooling like
go vetand the shadowing linter (go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest) to catch these issues before they become problems.
In short, := is Go’s way of cutting the ceremony and getting straight to the point. Use it widely, but use it wisely. Your code will be tighter and more readable for it.