Go has two ways to declare a variable: var, which works anywhere, and :=, a short form that works inside functions and infers the type.
Every variable has a fixed type decided at compile time. When you give a value, Go infers the type from it, so writing the type as well (var city string = "Lisbon") is legal but redundant.
var vs :=
var | := | |
|---|---|---|
| Where | package level and inside functions | inside functions only |
| Type | optional; required when there is no value | always inferred |
| Initial value | optional (zero value otherwise) | required |
| Redeclaring | never | allowed if at least one variable on the left is new |
The idiomatic choice inside a function:
x := valuewhen you have a starting value.var x Twhen you want the zero value, which signals "empty on purpose":var buf bytes.Buffer,var total int.var x T = valuewhen the inferred type is not the one you want, for examplevar ratio float64 = 1(plainratio := 1would be anint).
:= outside a function is a syntax error:
./main.go:5:1: syntax error: non-declaration statement outside function body
Zero Values
A variable declared without a value is never garbage. It holds its type's zero value:
| Type | Zero value |
|---|---|
| integers, floats | 0 |
bool | false |
string | "" |
| pointers, slices, maps, channels, functions, interfaces | nil |
| arrays, structs | every element or field set to its zero value |
Good Go APIs are designed so the zero value is useful: a zero sync.Mutex is unlocked, a zero bytes.Buffer is empty and ready, and len, range and append all work on a nil slice. The one to watch is the map: reading a nil map returns zero values, but writing to one panics. Create maps with make or a literal before storing into them.
Declaring Several Variables
The var ( ... ) block groups related package-level variables. In a multiple assignment like x, y = y, x, every expression on the right is evaluated before anything is assigned, which is why the swap works.
:= with several names redeclares nothing as long as at least one name on the left is new. Existing names in the same scope are simply assigned. This is what lets you reuse err:
f, err := os.Open("a.txt") // declares f and err
g, err := os.Open("b.txt") // declares g, assigns to the existing err
If none of the names are new, it is an error:
./main.go:7:4: no new variables on left side of :=
Use plain = to assign to a variable that already exists.
Unused Variables Are Errors
A local variable that is declared but never read stops the build:
./main.go:9:2: declared and not used: x
It is not a warning you can switch off. Delete the variable, use it, or, while debugging, silence it with the blank identifier: _ = x. The blank identifier also discards return values you do not need: _, err := fmt.Println("hi").
Package-level variables and function parameters are exempt from this rule.
Scope and Shadowing
A variable exists from its declaration to the end of the enclosing block ({ ... }). A := in an inner block creates a new variable, even if one with the same name exists outside. The outer one is shadowed, not changed:
The program prints inside: 42 and then outside: 0. The := inside the if declared both count and err as new variables in the inner block, because neither existed there yet. It compiles cleanly and is a classic source of "my value disappeared" bugs.
The fix is to declare err first and assign with =:
var err error
count, err = strconv.Atoi(input)
go vet does not report shadowing by default. The separate shadow analyzer does, and linters such as golangci-lint can enable it.
Naming
- Use
camelCase:maxRetries,userID. Notmax_retries. - Keep acronyms in one case:
userID,httpClient,parseURL, notuserId. - Short names for short scopes:
iin a loop,rfor a reader,errfor an error. Longer, descriptive names for package-level variables. - A capitalized name at package level (
var Timeout) is exported to other packages. See packages and imports.
Package-Level Variables
Variables declared outside functions live for the whole program and are initialized before main runs, in dependency order. They are convenient for configuration and caches, and they are shared by every goroutine. If more than one goroutine writes to one, protect it with a mutex or use sync/atomic, or you have a data race.
Prefer constants for values that never change. A const cannot be modified by accident and costs nothing at run time.
Frequently Asked Questions
What is the difference between var and := in Go?
:= declares and initializes a variable with an inferred type, and only works inside functions: count := 10. var works everywhere (including package level), lets you state the type, and can omit the initial value to get the zero value: var count int. Inside functions, idiomatic Go uses := when there is an initial value and var when you want the zero value.
What are zero values in Go?
Every variable declared without a value gets its type's zero value: 0 for numbers, false for bool, "" for string, and nil for pointers, slices, maps, channels, functions and interfaces. Structs get the zero value of each field. Go has no uninitialized variables.
How do I fix "declared and not used" in Go?
Go rejects a local variable that is assigned but never read. Use it, delete it, or, while debugging, assign it to the blank identifier with _ = x. Package-level variables and function parameters are exempt.
How do I declare a global variable in Go?
Use var at package level, outside any function: var counter int. The short form := is not allowed there. A capitalized name (var Counter int) is visible to other packages; a lowercase one is visible only in its own package. Guard package-level variables with a mutex if goroutines write to them.