go-declarations
Go Declarations and Initialization
Group Similar Declarations
Group related var, const, and type declarations in parenthesized blocks.
Bad:
import "a"
import "b"
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Good:
import (
"a"
"b"
)
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Only group related declarations. Separate unrelated ones into distinct blocks:
// Good: related constants grouped, unrelated constant separate
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
Groups work inside functions too. Group adjacent variable declarations even if unrelated:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5 * time.Second
err error
)
// ...
}
Top-level Variable Declarations
At the top level, use var. Do not specify the type unless it differs from the
expression's type:
Bad:
var _s string = F()
func F() string { return "A" }
Good:
var _s = F()
func F() string { return "A" }
Specify the type when the desired type differs from the expression:
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F returns myError but we want the error interface.
Local Variable Declarations
Default: Use
:=for local variables. Usevaronly for: zero-value initialization where the zero value matters, or when the type isn't clear from the right-hand side.
Use := when explicitly assigning a value:
// Bad
var s = "foo"
// Good
s := "foo"
Use var when the zero value is intentional—it signals "this starts empty on
purpose":
// Bad: empty literal hides the intent
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
// Good: var signals intentional nil slice
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Reduce Scope of Variables
Move declarations as close to usage as possible. Use if-init to limit scope:
Bad:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Good:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
Don't reduce scope if it forces deeper nesting. When you need a result outside
the if, declare it before:
// Good: data used after the error check
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
Move constants into functions when only used there:
// Good: constants scoped to the function that uses them
func Bar() {
const (
defaultPort = 8080
defaultUser = "user"
)
fmt.Println("Default port", defaultPort)
}
Initializing Structs
Always Use Field Names
Specify field names when initializing structs. Enforced by go vet:
// Bad
k := User{"John", "Doe", true}
// Good
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Exception: field names may be omitted in test tables with 3 or fewer fields.
Omit Zero-Value Fields
Let Go set zero values automatically. Only include fields that provide meaningful context:
// Bad
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
// Good
user := User{
FirstName: "John",
LastName: "Doe",
}
Use var for Zero-Value Structs
// Bad
user := User{}
// Good
var user User
Use &T{} for Struct References
Prefer &T{} over new(T) for consistency with struct initialization:
// Bad
sval := T{Name: "foo"}
sptr := new(T)
sptr.Name = "bar"
// Good
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Composite Literal Formatting
Use field names for external package types. Match closing brace indentation with
the opening line. Omit repeated type names in slice/map literals (gofmt -s).
Read references/INITIALIZATION.md when working with complex struct initialization, factory patterns, or builder patterns.
Initializing Maps
Use make() for empty maps that will be populated programmatically—it visually
distinguishes initialization from a nil declaration and allows size hints:
// Bad: empty literal looks too similar to nil declaration
var (
m1 = map[T1]T2{}
m2 map[T1]T2
)
// Good: make() is visually distinct
var (
m1 = make(map[T1]T2)
m2 map[T1]T2
)
Use map literals for fixed entries:
// Bad: programmatic insertion of static data
m := make(map[string]int, 3)
m["one"] = 1
m["two"] = 2
m["three"] = 3
// Good: literal for fixed entries
m := map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
Rule of thumb: literals for fixed data at init time, make for maps
populated later (with a size hint if known).
Use Raw String Literals
Use backtick strings to avoid hand-escaped characters:
// Bad
wantError := "unknown name:\"test\""
// Good
wantError := `unknown name:"test"`
Raw string literals can span multiple lines and include quotes, making them ideal for regex patterns, SQL, JSON, and multi-line text.
Use any Instead of interface{}
Go 1.18 introduced any as an alias for interface{}. Prefer any in new
code:
// Bad
func process(v interface{}) {}
// Good
func process(v any) {}
Avoid Using Built-In Names
Never use Go's predeclared identifiers as variable, function, or type names. Shadowing built-ins creates subtle bugs that the compiler won't catch.
Predeclared identifiers to avoid: error, string, bool, int,
float64, len, cap, append, copy, new, make, close, delete,
panic, recover, any, true, false, nil, iota.
Bad:
var error string
// error now shadows the builtin
func handleErrorMessage(error string) {
// error shadows the builtin inside this function
}
type Foo struct {
error error
string string // grepping for "error" or "string" becomes ambiguous
}
Good:
var errorMessage string
func handleErrorMessage(msg string) {
// error still refers to the builtin
}
type Foo struct {
err error
str string
}
Tools such as go vet can detect shadowing of predeclared identifiers.
Quick Reference
| Topic | Rule | Source |
|---|---|---|
| Grouping | Group related var/const/type; separate unrelated |
Uber |
| Top-level vars | Use var; omit type unless it differs |
Uber |
| Local vars | := for explicit values; var for intentional zero |
Uber |
| Variable scope | Move close to usage; use if-init | Uber |
| Struct init | Field names always; omit zero fields; var for zero struct |
Uber |
| Map init | make() for dynamic; literal for fixed |
Uber |
| Raw strings | Backticks for escapes, regex, multi-line | Uber |
any vs interface{} |
Prefer any in new code |
|
| Built-in names | Never shadow predeclared identifiers | Uber |
See Also
- go-style-core: Foundational style principles
- go-naming: Naming conventions including variable name length
- go-data-structures: Allocation with
newvsmake, slices, maps - go-control-flow: If-init patterns,
:=redeclaration rules - go-performance: Container capacity hints for maps and slices