Variables and constants¶
var — the long form¶
var declares one or more variables. Four shapes are supported.
// 1. Type with initializer.
var name string = "Ada"
var u, v, w float64 = -1.0, -2.0, -3.0
// 2. Type without initializer — variable takes its zero value.
var counter int // counter == 0
var greeting string // greeting == ""
var active bool // active == false
// 3. Initializer without explicit type — type is inferred.
var k = 0 // k is int
var pi = 3.14 // pi is float64
// 4. Grouped declarations (common at package level).
var (
host = "localhost"
port = 8080
verbose bool
)
Zero values¶
From the Go spec:
"Otherwise, each variable is initialized to its zero value."
There is no "uninitialized" state in Go — every variable always has a value.
| Type | Zero value |
|---|---|
numeric (int, float64, …) |
0 |
bool |
false |
string |
"" |
| pointer, slice, map, channel, function, interface | nil |
| struct | a struct whose fields each hold their own zero value |
var s []int // s == nil, len(s) == 0
var m map[string]int // m == nil — reading is fine, writing panics
var p *int // p == nil
:= — short declaration¶
Inside a function, := declares and assigns in one go. The type is always inferred.
func main() {
name := "Ada" // string
count := 0 // int
ratio := 0.5 // float64
items := []string{"a", "b"} // []string — a slice; covered later
fmt.Println(name, count, ratio, items)
}
Two rules to remember:
- Function-scope only.
:=is illegal at package level — there you must usevar. - Redeclaration is allowed if at least one name on the left is new and the reused names keep the same type:
From Python: Python lets you assign anywhere without ceremony. Go has two ways (
varand:=) because one is the only one allowed at package level and the other carries useful type-inference + multi-return ergonomics inside functions.
Multiple assignment and the blank identifier _¶
Like Python tuple unpacking, but for function returns:
n, err := strconv.Atoi("42")
if err != nil {
return err
}
fmt.Println(n)
_, err = io.Copy(dst, src) // ignore the byte count, keep the error
_ is the blank identifier — a write-only slot that lets you discard a value you don't need.
Shadowing pitfall¶
A new := in an inner scope creates a new variable that shadows the outer one. Read carefully:
err := doFirst()
if err != nil { return err }
if cond {
err := doSecond() // !!! NEW err, shadows the outer one
log.Println(err)
}
// outer err here is still the one from doFirst, NOT from doSecond.
This is one of the most common Go bugs. go vet's shadow analyzer and golangci-lint will warn you.
const — compile-time constants¶
Constants are evaluated at compile time. They can be typed or untyped.
const Pi float64 = 3.14159 // typed
const Greeting = "hello" // untyped string
const (
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
)
A constant cannot be assigned to. You also cannot take its address (&Pi is illegal).
Untyped constants are flexible — they take on the type they need at use:
const limit = 10
var i int = limit // limit used as int
var f float64 = limit // limit used as float64
var s string = "small"
const small = "small"
fmt.Println(s == small) // works — small fits string
If limit were declared as const limit int = 10, the second line would fail to compile — int is not assignable to float64 without explicit conversion.
iota — auto-incrementing constants¶
iota is a predeclared identifier that resets to 0 at the start of each const block and increments by 1 with each ConstSpec.
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
The expression on each line is implicitly repeated from the line above. You can use it with bit shifts for flag enums:
const (
ReadPerm = 1 << iota // 1 (iota == 0)
WritePerm // 2 (iota == 1)
ExecutePerm // 4 (iota == 2)
)
perms := ReadPerm | WritePerm // == 3
iota only exists inside const blocks — there is no general-purpose counter syntax.