๐Ÿ—๏ธStructsLESSON

Structs

Structs are Go's primary way to group related data together. Unlike classes in object-oriented languages, Go structs are simple data containers that you can attach behavior to via methods.

Defining a Struct

Use the type keyword to define a struct with named fields:

Each field has a name and a type. Fields are accessed and set individually.

Struct Literal Initialization

Create a struct value using named-field syntax โ€” this is the preferred approach because it is explicit and resilient to field reordering:

Access or mutate any field with the dot operator:

Value Receiver Methods

A method with a value receiver receives a copy of the struct. Changes made inside the method do not affect the original:

Use value receivers when the method only reads data and the struct is small. The caller's value is never modified.

Pointer Receiver Methods

A method with a pointer receiver receives a pointer to the original struct, so it can modify the caller's value:

Use pointer receivers when:

  • The method needs to mutate the struct's fields.
  • The struct is large and copying it on every call would be expensive.

Go automatically takes the address of p when you call a pointer receiver method on an addressable value, so p.Birthday() works even though p is not declared as a pointer.

Methods on Any Named Type

In Go, methods are not limited to structs. You can declare a method on any named type defined in the same package, including types based on primitives, slices, or maps.

The only restriction is that the named type must be declared in the same package as the method โ€” you cannot add methods to built-in types like int or string directly, because they are not yours to extend. You must first create a named alias:

Temperature types: type safety via named aliases

A classic example from the Go standard library idiom is temperature conversion. Even though both types are backed by float64, the compiler treats Celsius and Fahrenheit as completely distinct types:

Because Celsius and Fahrenheit are different types, the compiler will reject any attempt to add or compare them directly. This turns a common class of unit-mismatch bugs into a compile-time error โ€” the same kind of safety you would otherwise need a dedicated struct for, with far less ceremony.

Named slice type with a method

Methods on named slice types let you attach domain logic to a collection without wrapping it in a struct:

This pattern is used in the standard library itself โ€” for example, sort.StringSlice and sort.IntSlice are named slice types that implement sort.Interface through methods.

Embedding (Composition over Inheritance)

Go has no inheritance. Instead, you embed one struct inside another to reuse its fields and methods:

Employee automatically gets all of Person's fields and methods through promotion. This is composition, not inheritance: there is no parent/child relationship, no override chain, and no polymorphism unless you add an interface. You are simply embedding a value of type Person inside Employee and letting Go surface its members at the outer level for convenience.

This approach keeps types simple and explicit โ€” you always know exactly where a field or method comes from.

Struct Tags

Struct fields can carry metadata called tags, written as raw string literals after the type. They are used by packages like encoding/json and database/sql to control serialization:

Tags are pure metadata โ€” the compiler ignores them; only reflection-based packages read them at runtime.

Anonymous Structs

For one-off data shapes, you can define a struct inline without giving it a name:

Anonymous structs are handy for test table rows, temporary groupings, and HTTP request/response shapes that only appear once.

Struct Comparison

Two struct values are comparable with == if all their fields are comparable (no slices, maps, or functions). The comparison checks every field:

Comparable structs can also be used as map keys:

Knowledge Check

A value receiver method receives...

Which is correct struct literal syntax?

Which of these is a valid method declaration in Go?