Variables#
Go has three declaration forms (var, := short
declaration, const) and one identifier discard form
(_). Bindings are statically typed; the compiler infers the
type from the right-hand side when do not write
one.
/types`.
Declarations#
var x int // declared, zero-valued (0)
var y = 10 // type inferred (int)
var z int = 10 // both annotated and initialised
a := 10 // short declaration; function-scope only
const Pi = 3.14159 // constant; compile-time evaluated
const (
Pending = iota // 0
Active // 1
Closed // 2
)
The short form := works only inside functions; var is
the only form allowed at package scope.
Zero values#
Every binding starts at its zero value: 0 for numbers,
false for bool, "" for string, nil for pointers,
slices, maps, channels, interfaces, and functions. struct
zero values have each field zeroed in place.
var n int // 0
var s string // ""
var p *User // nil
var u User // User{Name: "", Age: 0}
Zero values make declared-but-unassigned bindings safe to use.
Multiple assignment#
a, b := 1, 2
a, b = b, a // swap
x, y, z := 1, 2, 3
x, _, z := 1, 2, 3 // discard the middle value
Functions return multiple values; the operator destructures with the same form.
value, err := strconv.Atoi("42")
if err != nil { return err }
_, err = os.Stat(path) // value discarded
Scope#
Go is lexically scoped. var and := introduce a binding
that is visible from the next statement to the end of the
enclosing block (function, if, for, switch,
select, or a bare { ... } block).
{
secret := "rk"
fmt.Println(secret) // visible
}
fmt.Println(secret) // compile error; out of scope
if and for accept an optional init statement that
binds a variable scoped to the conditional body.
if v, err := load(); err == nil {
use(v) // v in scope here
} // v out of scope here
for i := 0; i < 10; i++ {
// i in scope here only
}
Shadowing#
:= re-declares a binding when at least one name on the left
is new. The operator watches for accidental shadows inside
if blocks; go vet -shadow catches them.
err := load()
if err != nil {
err := wrap(err) // SHADOWS the outer err
return err // returns the inner; outer unchanged
}
const and iota#
Constants are typed or untyped at the compile level; untyped
constants get their type from the context they are used in.
iota is a per-block counter that resets at each const ( … )
group, useful for enumerations.
const (
KB = 1 << (10 * (iota + 1)) // 1<<10 = 1024
MB // 1<<20
GB // 1<<30
)
const (
_ = iota // skip 0
Red // 1
Green // 2
Blue // 3
)
Package-level state#
Package-level var declarations are visible to every file in
the package. The operator keeps these minimal; tests prefer
construction over global mutable state.
// logger is the package-wide structured logger.
var logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
Initialise package-level state in func init() when the
expression cannot be a constant.
var rng *rand.Rand
func init() {
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
}
Pointers#
Pointers hold an address. &x takes the address of x;
*p reads through the pointer. Method receivers and large
struct passes use pointers; everything else passes by value.
var x int = 10
p := &x // *int
*p = 20 // x is now 20
u := &User{Name: "rk"} // pointer-to-struct literal
u.Name = "operator" // automatic deref through . operator
Pointer dereference is automatic on field access (u.Name
works whether u is User or *User).