
Go Declarations
- 659 installs
- 137 repo stars
- Updated June 20, 2026
- cxuu/golang-skills
go-declarations is a Claude Code skill that enforces idiomatic Go variable, constant, struct, map, and iota enum declarations for developers writing backend Go 1.18+ code with consistent initialization style.
About
go-declarations is a Go style skill from cxuu/golang-skills focused on declaration and initialization idioms. It routes agents to references/SCOPE.md for var versus :=, if-init narrowing, and composite literal formatting, plus references/IOTA.md for constant blocks and enumerated types. Examples use any instead of interface{}, requiring Go 1.18+. Developers reach for go-declarations when creating new structs, const blocks, or maps even if they do not explicitly ask about style, because the skill prevents scope leaks and non-idiomatic patterns before review. It pairs with go-naming for identifiers but owns declaration mechanics exclusively.
- Enforces var vs := rules including top-level, zero-value intent, and type-expression mismatch cases
- Guides narrow scoping with if-init statements and reduces variable lifetime
- Formats composite literals with keyed fields and consistent struct/map initialization
- Designs clean iota-based enums and replaces interface{} with any (Go 1.18+)
- References 6 focused SCOPE, IOTA, INITIALIZATION, LITERALS, STRUCTS, and SHADOWING guides
Go Declarations by the numbers
- 659 all-time installs (skills.sh)
- Ranked #563 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cxuu/golang-skills --skill go-declarationsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 659 |
|---|---|
| repo stars | ★ 137 |
| Last updated | June 20, 2026 |
| Repository | cxuu/golang-skills ↗ |
When should Go code use var versus :=?
Get consistent, idiomatic Go declarations and initializations every time they create variables, constants, structs, maps, or iota enums.
Who is it for?
Go backend developers writing new structs, maps, or const blocks who want consistent declaration style aligned with Go 1.18+ idioms.
Skip if: Developers needing Go naming conventions, package layout guidance, or concurrency patterns outside declaration and initialization syntax.
When should I use this skill?
User writes Go variables, constants, structs, maps, iota enums, or asks about var versus := and if-init scope.
What you get
Idiomatic Go declarations with correct scope, composite literals, and iota enum constant blocks.
- idiomatic Go declarations
- iota enum blocks
- scoped variable initializations
By the numbers
- Includes references/SCOPE.md and references/IOTA.md guidance files
- Examples require Go 1.18+ for any instead of interface{}
Files
Go Declarations and Initialization
Compatibility: Examples may use any, which requires Go 1.18+.Resource Routing
references/SCOPE.md- Read when deciding betweenvar,:=, if-init, and narrow variable scope.references/IOTA.md- Read when designing constants or enum-like values.references/INITIALIZATION.md- Read when initializing structs, maps, zero values, or pointers.references/LITERALS.md- Read for composite literal formatting and keyed-field tradeoffs.references/STRUCTS.md- Read when designing or initializing structs.references/SHADOWING.md- Read when a declaration may shadow a builtin or outer variable.
Quick Reference: var vs :=
| Context | Use | Example |
|---|---|---|
| Top-level | var (always) | var _s = F() |
| Local with value | := | s := "foo" |
| Local zero-value (intentional) | var | var filtered []int |
| Type differs from expression | var with type | var _e error = F() |
---
Group Similar Declarations
Group related var, const, type in parenthesized blocks. Separate unrelated declarations into distinct blocks.
// Bad
const a = 1
const b = 2
// Good
const (
a = 1
b = 2
)Inside functions, group adjacent vars even if unrelated:
var (
caller = c.name
format = "json"
timeout = 5 * time.Second
)---
Constants and iota
Start enums at one so the zero value represents invalid/unset:
const (
Add Operation = iota + 1
Subtract
Multiply
)Use zero when the default behavior is desirable (e.g., LogToStdout).
---
Variable Scope
Use if-init to limit scope when the result is only needed for the error check:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}Don't reduce scope if it forces deeper nesting or you need the result outside the if. Move constants into functions when only used there.
---
Initializing Structs
- Always use field names (enforced by
go vet). Exception: test tables
with ≤3 fields.
- Omit zero-value fields — let Go set defaults.
- Use `var` for zero-value structs:
var user Usernotuser := User{} - Use `&T{}` over `new(T)`:
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).
---
Initializing Maps
| Scenario | Use | Example |
|---|---|---|
| Empty, populated later | make(map[K]V) | m := make(map[string]int) |
| Nil declaration | var | var m map[string]int |
| Fixed entries at init | Literal | m := map[string]int{"a": 1} |
make() visually distinguishes empty-but-initialized from nil. Use size hints when the count is known.
---
Raw String Literals
Use backtick strings to avoid hand-escaped characters:
// Bad
wantError := "unknown name:\"test\""
// Good
wantError := `unknown name:"test"`Ideal for regex, SQL, JSON, and multi-line text.
---
Prefer any Over interface{}
Go 1.18+: use any instead of interface{} in all new code.
---
Avoid Shadowing Built-In Names
Never use predeclared identifiers (error, string, len, cap, append, copy, new, make, close, delete, panic, recover, any, true, false, nil, iota) as names. Use go vet to detect.
// Bad — shadows the builtin
var error string
// Good
var errorMessage string---
Related Skills
- Naming conventions: See go-naming when choosing variable names, constant names, or deciding name length by scope
- Data structures: See go-data-structures when choosing between
newandmake, or initializing slices and maps - Control flow scoping: See go-control-flow when using if-init,
:=redeclaration, or avoiding variable shadowing - Capacity hints: See go-performance when pre-allocating maps or slices with known sizes
Composite Literal Formatting
Source: Google Go Style Guide (decisions.md)
Detailed rules for formatting composite literals (struct, slice, map) in Go.
---
Field Names
Struct literals must specify field names for types defined outside the current package:
// Good: external package type — use field names
r := csv.Reader{
Comma: ',',
Comment: '#',
FieldsPerRecord: 4,
}
// Bad: positional — fragile and unreadable
r := csv.Reader{',', '#', 4, false, false, false, false}For package-local types, field names are optional but recommended when the struct has many fields:
// Acceptable: small internal type
okay := Type{42}
// Recommended: many fields
okay := StructWithLotsOfFields{
field1: 1,
field2: "two",
field3: 3.14,
field4: true,
}---
Matching Braces
The closing brace must appear on a line with the same indentation as the opening brace. Don't put the closing brace on the same line as a value in a multi-line literal:
// Good
[]*Type{
{Key: "multi"},
{Key: "line"},
}
// Bad: closing brace on value line
[]*Type{
{Key: "multi"},
{Key: "line"}}---
Cuddled Braces
Dropping whitespace between braces ("cuddling") is permitted only when both:
1. Indentation matches 2. Inner values are also literals (not variables or expressions)
// Good: cuddled braces with literal inner values
[]*Type{{
Field: "value",
}, {
Field: "value",
}}
// Bad: cuddled with non-literal inner value
[]*Type{
first,
{
Field: "second",
}}---
Repeated Type Names
Repeated type names may be omitted in slice and map literals:
// Good: type names omitted (cleaner)
[]*Type{
{A: 42},
{A: 43},
}
// Bad: redundant type names
[]*Type{
&Type{A: 42},
&Type{A: 43},
}Tip: Run gofmt -s to remove repetitive type names automatically.
---
Zero-Value Fields
Omit zero-value fields when doing so does not reduce clarity. Well-designed APIs use zero-value construction to draw attention to the options being specified:
// Good: zero fields omitted, important ones stand out
ldb := leveldb.Open("/my/table", &db.Options{
BlockSize: 1 << 16,
ErrorIfDBExists: true,
})
// Bad: noise from zero fields
ldb := leveldb.Open("/my/table", &db.Options{
BlockSize: 1 << 16,
ErrorIfDBExists: true,
BlockRestartInterval: 0,
// ... all zero fields listed ...
})Exception: table-driven test structs often benefit from explicit field names even for zero values to clarify the test case.
Constants and iota Patterns
Source: Uber Style Guide, Google Style Guide
Detailed patterns for designing enumerated constants with iota in Go.
---
Start Enums at One
Start enums at one so the zero value represents an invalid/unset state. This catches uninitialized variables:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
// Add=1, Subtract=2, Multiply=3When Zero Makes Sense
Use zero when the default behavior is desirable:
type LogOutput int
const (
LogToStdout LogOutput = iota // zero value = default
LogToFile
LogToRemote
)The key question: is the zero value a valid, useful default? If yes, start at zero. If no, start at one.
---
Bitmask Patterns
Use bit-shifting with iota for flag/bitmask enums:
type Permission int
const (
Read Permission = 1 << iota // 1
Write // 2
Execute // 4
)
// Combine with bitwise OR
perms := Read | Write // 3---
Byte Size Pattern
A common pattern for byte size constants:
type ByteSize float64
const (
_ = iota // ignore first value (0)
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
)---
String Representation
Always implement String() for enum types to aid debugging:
func (o Operation) String() string {
switch o {
case Add:
return "Add"
case Subtract:
return "Subtract"
case Multiply:
return "Multiply"
default:
return fmt.Sprintf("Operation(%d)", o)
}
}Consider using go generate with stringer for automatic string methods on large enums.
---
Grouping Rules
- Each enum type gets its own
constblock —iotaresets to 0 in each block - Unrelated constants go in separate blocks
- Document the enum type, not each individual constant (unless behavior is
non-obvious)
Composite Literal Formatting
Sources: Google Go Style Guide (decisions), Effective Go.
---
Struct Literal Rules
Always use field names for struct literals. This is enforced by go vet for types from other packages and prevents breakage when fields are reordered.
// Good: Named fields
cfg := Config{
Host: "localhost",
Port: 8080,
Timeout: 30 * time.Second,
}
// Bad: Positional — fragile and unreadable
cfg := Config{"localhost", 8080, 30 * time.Second}When field names may be omitted:
- Test table rows with 3 or fewer fields where meaning is obvious
- Coordinate-like types:
image.Point{0, 0},color.RGBA{255, 0, 0, 255} - Types where field order is part of the documented API
---
Multi-Line vs Single-Line Structs
Single-line for 1-2 short fields:
p := Point{X: 1, Y: 2}
e := Entry{Key: "name", Value: "Alice"}Multi-line for 3+ fields, long values, or when readability benefits:
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}The closing brace sits on its own line, aligned with the opening identifier.
---
Cuddled Braces
When a composite literal is used as a function argument or assignment, the opening brace "cuddles" with the preceding token — no line break between them:
// Good: Brace cuddles with the function call
db.SetConnMaxLifetime(ConnConfig{
MaxOpen: 25,
MaxIdle: 5,
MaxLifetime: 5 * time.Minute,
})
// Bad: Unnecessary line break before brace
cfg :=
Config{
Verbose: true,
Output: os.Stdout,
}---
Slice Literal Formatting
Short slices fit on one line:
primes := []int{2, 3, 5, 7, 11}Long slices use one element per line with a trailing comma:
endpoints := []string{
"/api/users",
"/api/posts",
"/api/comments",
"/healthz",
}Omitting Repeated Type Names
Omit the element type name in slice literals — gofmt -s removes them:
// Good: Type name omitted
items := []Item{
{Name: "widget", Price: 9.99},
{Name: "gadget", Price: 19.99},
}
// Bad: Redundant type names
items := []Item{
Item{Name: "widget", Price: 9.99},
Item{Name: "gadget", Price: 19.99},
}The same applies to pointer slices — use {...} not &Node{...}.
Map Literal Formatting
Short maps fit on one line:
counts := map[string]int{"a": 1, "b": 2}Multi-line maps use one entry per line with trailing commas:
headers := map[string]string{
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
"X-Request-ID": reqID,
}---
Function Literal Formatting
Short closures can stay on one line when used as arguments:
sort.Slice(items, func(i, j int) bool { return items[i].Name < items[j].Name })Multi-line closures follow standard indentation:
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})If a closure exceeds ~15 lines or captures many variables, extract it into a named function for readability and testability.
---
Multi-Line Wrapping Rules
When a composite literal doesn't fit on one line, follow these rules:
1. Opening brace stays on the same line as the declaration or call 2. Each element on its own line, indented one level, with trailing comma 3. Closing brace on its own line at the original indentation level
// Good: Follows all wrapping rules
resp := &Response{
StatusCode: http.StatusOK,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: mustMarshal(data),
}Go requires the trailing comma when the closing brace is on a separate line.
Variable Scope and Declaration Patterns
Source: Uber Style Guide, Google Style Guide
Detailed patterns for choosing between var and := and reducing variable scope in Go.
---
Top-Level Declarations
At the top level, always use var. Do not specify the type unless it differs from the expression's type:
// Bad: redundant type
var _s string = F()
// Good: type inferred
var _s = F()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{} }
// F returns myError but we want the error interface
var _e error = F()---
Local Variable Patterns
Use := with explicit values
// Bad
var s = "foo"
// Good
s := "foo"Use var for intentional zero values
var signals "this starts empty on purpose":
// Bad: empty literal hides intent
filtered := []int{}
// Good: var signals intentional nil slice
var filtered []intThis is especially important for slices: []int{} marshals to [] in JSON while nil marshals to null. Choose based on your API contract.
Type annotation when RHS is unclear
Use var with an explicit type when the type isn't obvious from the right-hand side:
// Type not obvious from function name alone
var ratio float64 = computeRatio()---
Reducing Scope
If-init pattern
Move declarations as close to usage as possible. Use if-init to limit scope:
// Bad: err lives beyond where it's needed
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
// Good: err scoped to the if block
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}When NOT to reduce scope
Don't reduce scope if it forces deeper nesting or if you need the result after the if:
// 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)Scope constants to functions
Move constants into functions when only used there:
func Bar() {
const (
defaultPort = 8080
defaultUser = "user"
)
fmt.Println("Default port", defaultPort)
}---
Decision Tree: var vs :=
Is it top-level?
├── Yes → use var
└── No (local)
├── Assigning a value? → use :=
├── Intentional zero value? → use var
└── Type differs from RHS? → use var with typeVariable Shadowing
Normative: Be aware that := in inner scopes creates a new variable that shadows the outer one.The Trap
// Bug: err in the inner scope shadows the outer err
var err error
if condition {
val, err := someFunc() // new err — outer err stays nil
use(val)
}
return err // always nil!Fix: Assign to the Outer Variable
var err error
if condition {
var val int
val, err = someFunc() // assigns to outer err
use(val)
}
return err // correctDetection
Enable the shadow linter via go vet:
go vet -vettool=$(which shadow) ./...Or add govet with shadow check enabled in .golangci.yml.
Struct Initialization
Source: Uber Style Guide, Google Style Guide
Detailed rules and patterns for initializing Go structs.
---
Always Use Field Names
Specify field names when initializing structs. This is enforced by go vet for external package types:
// Bad: positional — fragile, breaks when fields are added/reordered
k := User{"John", "Doe", true}
// Good: named fields — clear and resilient to changes
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}Exception: Field names may be omitted in test tables with 3 or fewer fields:
tests := []struct {
input string
expected int
}{
{"abc", 3},
{"", 0},
}---
Omit Zero-Value Fields
Let Go set zero values automatically. Only include fields that provide meaningful context:
// Bad: noise from zero fields
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
// Good: zero fields omitted, important ones stand out
user := User{
FirstName: "John",
LastName: "Doe",
}Exception: Table-driven test structs often benefit from explicit field names even for zero values to clarify the test case.
---
Use var for Zero-Value Structs
Signal that a zero-value struct is intentional with var:
// Bad: empty literal is ambiguous — forgot fields or intentional?
user := User{}
// Good: var clearly signals "zero value on purpose"
var user User---
Use &T{} for Struct References
Prefer &T{} over new(T) for consistency with struct initialization:
// Bad: new() then set fields separately
sptr := new(T)
sptr.Name = "bar"
// Good: initialize inline
sptr := &T{Name: "bar"}Both &T{} and new(T) produce a pointer to a zero-value T, but &T{} allows inline field initialization.
---
Multi-Line vs Single-Line
Use single-line for structs with 1-2 short fields:
p := Point{X: 1, Y: 2}Use multi-line for 3+ fields or long values:
cfg := Config{
Host: "localhost",
Port: 8080,
Timeout: 30 * time.Second,
}---
Pointer to Struct Literals in Slices
When building slices of struct pointers, omit the repeated type name:
// Good
items := []*Item{
{Name: "a", Value: 1},
{Name: "b", Value: 2},
}
// Bad: redundant type names
items := []*Item{
&Item{Name: "a", Value: 1},
&Item{Name: "b", Value: 2},
}Run gofmt -s to clean these up automatically.
Related skills
How it compares
Use go-declarations for initialization idioms and pair go-naming when identifier naming also needs review.
FAQ
What Go version does go-declarations require?
go-declarations examples use any instead of interface{}, which requires Go 1.18 or newer. The skill covers var, :=, if-init scope, composite literals, and iota enum patterns for backend code.
Does go-declarations cover Go naming rules?
go-declarations focuses on declaration and initialization idioms such as var versus := and iota enums. Naming conventions are handled by the separate go-naming skill in cxuu/golang-skills.