GGistDev

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