Structs in Go
Structs are collections of named fields. They are the primary way to model data and can compose behavior via embedding.
Define and initialize
Define a named struct type and create values using composite literals. Unspecified fields take the zero value. Prefer keyed fields for clarity and resilience to field reordering.
type User struct {
ID int
Name string
Email string
}
u := User{ID: 1, Name: "Alice"}
var v User // zero values
p := &User{ID: 2}
Methods
Attach behavior to types with methods. Use pointer receivers to mutate or avoid copying large structs; value receivers are fine for small, immutable data.
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func (c Counter) Val() int { return c.n }
Field tags
Tags are string literals associated with fields, used by libraries (e.g., JSON, DB). They follow the form json:"name,omitempty".
type Product struct {
ID int `json:"id" db:"id"`
Name string `json:"name"`
}
Embedding (composition)
Embedding composes behavior by placing one struct (or interface) into another without a field name. Fields and methods are promoted.
type Logger struct{}
func (Logger) Log(msg string) {}
type Service struct {
Logger // embedded
Name string
}
func main() {
s := Service{Name: "users"}
s.Log("starting") // promoted method
}
Field/method promotion
- Embedded fields/methods are available on the outer type
- You can still access them explicitly:
s.Logger.Log(...) - Method promotion follows receiver compatibility; pointer receiver methods require the outer value to be addressable
Conflicts
type A struct{ X int }
type B struct{ X int }
type C struct{
A
B
}
// c.X is ambiguous; use c.A.X or c.B.X
If both embedded types define the same method, the conflict must be resolved explicitly.
Embedding interfaces
type Reader interface{ Read([]byte) (int, error) }
type Writer interface{ Write([]byte) (int, error) }
type ReadWriter interface{
Reader
Writer
}
Compare and assign
- Comparable if all fields are comparable
- Assignment copies all fields; use pointers for shared/mutable access
type P struct{ X, Y int }
a := P{1,2}
b := a // copy
b.X = 9 // does not change a.X
Constructors (idiomatic)
func NewUser(id int, name string) *User { return &User{ID: id, Name: name} }
Use constructors to enforce invariants and hide internal fields.
Patterns and best practices
- Use embedding for composition (logging, audit, metrics), not inheritance
- Prefer small, focused embedded types; be explicit when ambiguity arises
- Keep structs cohesive; prefer explicit field names in literals
- Use pointers for large structs or when mutation/sharing is intended
- Document invariants and ownership
- Export only what you need; keep fields unexported and expose methods for validation
Summary
- Structs model data with named fields, tags, methods, and embedding for composition