Go

Go Structs & Methods👨‍💻

Go has no classes, no inheritance, and no constructors. It has structs and methods. A struct is a typed collection of fields. A method is a function with a receiver argument. Together, they provide everything you need to model domain objects, encapsulate behavior, and satisfy interfaces -- without a class hierarchy.

Composition replaces inheritance through struct embedding. When you embed a type inside another struct, its methods are promoted to the outer type. This means the outer type automatically satisfies any interface the embedded type satisfies. There is no fragile base class problem, no diamond inheritance, and no confusion about which superclass method gets called. The embedded type's methods always operate on the embedded value, and the outer type can override any promoted method by defining its own.

The choice between pointer and value receivers is one of the most important decisions in Go API design. It affects mutability, interface satisfaction, memory allocation, and whether nil receivers are possible. Struct tags provide metadata for serialization, validation, and database mapping. Constructor functions (the NewX convention) handle initialization logic that zero values cannot express. These mechanisms are simple individually, but combining them well is what makes Go code clean and maintainable.

Key Takeaways

  • 1Structs are value types -- assignment copies all fields. Use pointers (*T) when you need shared references or want to avoid copying large structs.
  • 2Methods are functions with a receiver. Pointer receivers (*T) can mutate the receiver; value receivers (T) work on a copy and cannot.
  • 3Struct embedding promotes the embedded type's methods and fields, enabling composition over inheritance with zero boilerplate.
  • 4Method sets determine interface satisfaction: a value of type T can only call value receivers, but a *T can call both pointer and value receivers.
  • 5Struct tags (backtick annotations) drive JSON marshaling, database mapping, and validation -- they are the primary metadata mechanism in Go.
  • 6The NewX constructor pattern handles complex initialization, enforces invariants, and returns a pointer to the newly created struct.

Master go structs & methods

Take the Go Programming course with hands-on lessons and challenges.

Examples

Struct declaration and the NewX constructor pattern

go

NewServer is a constructor function that enforces required parameters (host, port) and applies sensible defaults for optional ones. It returns a pointer because Start() mutates the struct. Unexported fields (startedAt) are only accessible within the package, providing encapsulation without access modifiers.

Pointer vs value receivers -- mutation semantics

go

Value receivers (Point) work on a copy -- the caller's variable is never changed. Pointer receivers (*Point) modify the original. The general rule: use pointer receivers when the method mutates state or when the struct is large enough that copying is expensive. Use value receivers for small, immutable data. Be consistent within a type -- if one method needs a pointer receiver, give all methods pointer receivers.

Struct embedding -- composition over inheritance

go

Service embeds *Logger and *Metrics, promoting their methods. You call svc.Log() and svc.Inc() directly, as if they were defined on Service. This is composition, not inheritance -- Logger and Metrics are independent, reusable components. Service can override any promoted method by defining its own with the same name.

Struct tags for JSON, database, and validation

go

Struct tags are string literals attached to fields that provide metadata for serialization and other tools. json:"-" excludes Password from JSON output entirely. omitempty skips zero-value fields. Multiple tag systems (json, db, validate) can coexist on the same field. The encoding/json package reads these tags via reflection at runtime.

Method sets and interface satisfaction

go

A value of type T can only call value receivers. A value of type *T can call both pointer and value receivers. This matters for interface satisfaction: Document (value) satisfies Sizer but not StringSizer. *Document (pointer) satisfies both. This is why most Go types use pointer receivers consistently -- it avoids surprising interface satisfaction failures.

Anonymous structs for test fixtures and one-off data

go

Anonymous structs are declared inline without a type name. They are idiomatic for table-driven tests (the []struct pattern is everywhere in Go test files), one-off JSON decoding when you only need a few fields from a large payload, and quick API responses. They avoid polluting the package namespace with types used in only one place.

Common Mistakes

Mistake:

Mixing pointer and value receivers on the same type without understanding the consequences for interface satisfaction

Fix:

Be consistent. If any method needs a pointer receiver (because it mutates state or the struct is large), make all methods use pointer receivers. Mixing means values of that type can only satisfy interfaces defined by the value-receiver methods, which leads to confusing compile errors when you pass a value where a pointer is expected.

Mistake:

Embedding a mutex by value and then copying the struct, which copies the mutex state

Fix:

Always embed sync.Mutex (and types containing one) as a field, not by value in copied structs. If your struct is ever assigned or passed by value, the mutex gets copied in an inconsistent state. Use pointer receivers on all methods of structs containing a mutex, and consider using go vet to detect this.

Mistake:

Using struct embedding to simulate inheritance, then being surprised when the embedded type's methods do not have access to the outer struct's fields

Fix:

Embedding is delegation, not inheritance. When you call an embedded method, its receiver is the embedded value, not the outer struct. The embedded Logger does not know about the Service that contains it. If you need the outer context, pass it explicitly or use an interface instead of embedding.

Mistake:

Forgetting that json:"-" and json:",omitempty" behave differently -- omitempty still includes the field if it has a non-zero value

Fix:

json:"-" always excludes the field from JSON output. json:",omitempty" excludes it only when the value is the zero value for its type. For sensitive fields like passwords, always use json:"-". Use omitempty for optional fields that should appear when set.

Best Practices

  • Use the NewX constructor pattern when zero values are not valid, when fields need validation, or when you need to set defaults. Return a pointer if any method uses a pointer receiver.
  • Keep structs focused on a single responsibility. Prefer multiple small structs composed via embedding over one large struct with many fields.
  • Be consistent with receiver types within a single struct. If one method needs a pointer receiver, use pointer receivers for all methods on that type.
  • Use struct tags deliberately: json for API serialization, db for database mapping, validate for input validation. Align tags vertically for readability in large structs.
  • Prefer embedding interfaces over concrete types when the outer struct only needs to expose the interface's method set. This provides flexibility for testing and future changes.

Summary

Go structs and methods replace classes and inheritance with a simpler, more explicit model. Structs hold data, methods add behavior via receivers, and embedding provides composition without the pitfalls of class hierarchies. Pointer receivers enable mutation and consistent interface satisfaction. Struct tags drive serialization and metadata. The NewX constructor pattern handles initialization. These building blocks combine to produce Go's distinctive style: explicit, composable, and free of hidden control flow.

Practice Go with hands-on challenges

Learn go structs & methods hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Go with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.