← Go, End to End

Go, End to End

Structs and Methods

A struct is a typed collection of fields. It's Go's only real way to group related data together, and once you add methods to it, it becomes the closest thing Go has to a class, minus the inheritance.

Declaring and initializing structs

A struct type lists its fields with names and types. You can build a value three ways: a positional literal (fragile, breaks if field order changes), a named-field literal (the one you should default to), or a zero-value declaration where every field gets its type's zero value.

go · live, editable, runnable

Named-field literals also let you skip fields you don't care about, they just take the zero value. That alone is a good reason to prefer them once a struct has more than two or three fields.

Value receivers vs pointer receivers

Methods in Go are just functions with an extra receiver argument bolted on before the name. The receiver can be a value or a pointer, and the choice matters more than it looks like it should.

go
func (p Point) Scaled(factor int) Point { // value receiver: operates on a copy
	return Point{p.X * factor, p.Y * factor}
}

func (p *Point) Scale(factor int) { // pointer receiver: mutates the real thing
	p.X *= factor
	p.Y *= factor
}

A value receiver method gets a copy of the struct. If it mutates that copy, the mutation vanishes the moment the method returns, the caller's original value is untouched. This is the single most common "why isn't my struct changing" bug in beginner Go code.

go · live, editable, runnable

Go automatically takes the address for you when you call a pointer-receiver method on an addressable value, that's why `c.Increment()` above works even though `c` isn't literally `&c`. It only fails if `c` isn't addressable, like a map value or the result of a function call.

The rule of thumb

  • If any method on the type needs a pointer receiver (because it mutates), make every method on that type use a pointer receiver, for consistency.
  • If the struct is small (a couple of machine words, like Point above) and never mutated, value receivers are fine and slightly cheaper to reason about.
  • If the struct is large or you're not sure, default to a pointer receiver. Copying a big struct on every method call is wasted work for no benefit.

Struct embedding

Go doesn't have inheritance, but it has embedding, and the two get confused constantly because the syntax looks similar to what other languages use for "extends". Embedding is composition with some syntax sugar: the outer struct gets direct access to the embedded struct's fields and methods, as if they were promoted up a level.

go · live, editable, runnable

What embedding does not give you is polymorphism. There's no way to write a function that takes an `Animal` and have it accept a `Dog` in its place just because `Dog` embeds `Animal`, Go has no subtyping relationship between them at all. If you want that kind of substitutability, you want an interface, covered in the next chapter, not embedding.

Struct tags

A struct tag is a string literal attached to a field, sitting in backticks right after its type. The language itself does nothing with tags, they're pure metadata. Packages that use reflection, most famously `encoding/json`, read them at runtime to decide how to treat each field.

go
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email,omitempty"`
	admin bool   // unexported: encoding/json will skip this entirely, no tag needed
}

The tag format itself is just a space-separated list of `key:"value"` pairs, and it's up to whatever library reads it to define what keys mean anything. `encoding/json` cares about `json:"..."`, a validation library might look for `validate:"..."` on the very same field, and they coexist fine since each library only reads its own key.

Try it yourself: value vs pointer receiver mutation

Predict the output before running, then check yourself:

go · live, editable, runnable

If you predicted 150 then 120, go back and fix Deposit to use a pointer receiver, then rerun.