Skip to content

Structs and Methods

Scope: grouping data with structs, attaching behavior with methods, and how Go does "objects" without classes. All examples were compiled and run.

Structs: named bundles of data

type User struct {
    Name   string
    Age    int
    Active bool
}

u := User{Name: "Ada", Age: 36, Active: true}
fmt.Println(u.Name)  // Ada
  • type User struct defines the shape.
  • Field names are capitalized here, so they're exported — another package could read u.Name. Lowercase fields stay private.
  • Initialize with field names (User{Name: ...}) — order doesn't matter and it's self-documenting. The positional form (User{"Ada", 36, true}) exists but is fragile; prefer names.

Methods: functions with a receiver

func (u User) Greeting() string {
    return "hi, I'm " + u.Name
}

fmt.Println(u.Greeting())

The (u User) part is the receiver — it attaches the function to the User type. u.Greeting() reads naturally.

Value vs pointer receivers

func (u User) Birthday() {       // value receiver: works on a copy
    u.Age++                      // modifies the copy — lost!
}

func (u *User) Birthday() {      // pointer receiver: modifies the original
    u.Age++
}
  • Value receiver (u User) — the method gets a copy. Good for read-only methods on small structs.
  • Pointer receiver (u *User) — the method can modify the original. Required when the method changes the struct.

Rule of thumb: if any method on a type needs a pointer receiver, give them all pointer receivers for consistency. Go handles the & automatically at the call site — u.Birthday() works whether u is a value or pointer.

Constructors: just functions

Go has no constructors. You write a function:

func NewUser(name string, age int) *User {
    return &User{Name: name, Age: age, Active: true}
}

u := NewUser("Ada", 36)

The New prefix is convention, not syntax. Returning a pointer (*User) avoids copying.

Embedding: Go's answer to inheritance

Instead of inheritance, Go embeds one struct inside another:

type Employee struct {
    User          // embedded — Employee gets User's fields and methods
    EmployeeID string
}

e := Employee{User: User{Name: "Ada", Age: 36}, EmployeeID: "E42"}
fmt.Println(e.Name)        // "Ada" — promoted from User
fmt.Println(e.Greeting())  // works too

Composition, not inheritance. Employee has a User; it doesn't derive from one. You can't override methods — if both types define Greeting, the outer one wins, explicitly. This avoids entire categories of inheritance headaches.

No classes, and that's fine

Go's model is deliberately flat:

Other languages Go
Class with fields + methods Struct + methods
Inheritance Embedding (composition)
Constructor NewXxx function
public/private keywords Capitalization

Check

  • [ ] You can define a struct, build one, and add a method
  • [ ] You can explain value vs pointer receivers and pick correctly
  • [ ] You can explain embedding vs inheritance in one sentence

Next: Interfaces.