Structs

Go has no classes. A struct is Go's way of grouping related fields into one named type, and methods get attached to that type separately, outside the struct definition itself.

Defining and creating a struct

Go main.go
package main

import "fmt"

type Account struct {
	Owner   string
	Balance float64
}

func main() {
	acc := Account{Owner: "Jamie", Balance: 100}
	fmt.Println(acc.Owner, acc.Balance)
}
Output
Jamie 100

type Account struct { ... } declares a new type with two named fields. Account{Owner: "Jamie", Balance: 100} creates a value of that type, and . reaches into a field exactly like in most other languages here.

Note: a capitalized field name like Owner is exported — visible outside the package it's defined in. A lowercase name like owner would only be visible inside the same package. Go uses capitalization itself as the public/private marker, instead of keywords like public or private.

Methods with a receiver

A method is a function with an extra receiver argument before its name, tying it to a particular type:

Go main.go
package main

import "fmt"

type Account struct {
	Owner   string
	Balance float64
}

func (a Account) Describe() string {
	return fmt.Sprintf("%s has $%.2f", a.Owner, a.Balance)
}

func main() {
	acc := Account{Owner: "Jamie", Balance: 100}
	fmt.Println(acc.Describe())
}
Output
Jamie has $100.00

func (a Account) Describe() string attaches Describe to the Account type — a is the receiver, a local name for whichever Account value the method was called on, similar to this or self in other languages but declared explicitly instead of implied.

Value receivers copy; pointer receivers don't

Describe above uses a value receiver, which means a is a full copy of the struct — fine for reading, but a method that needs to actually change the struct needs a pointer receiver instead:

Go main.go
package main

import "fmt"

type Account struct {
	Owner   string
	Balance float64
}

func (a *Account) Deposit(amount float64) {
	a.Balance += amount
}

func main() {
	acc := Account{Owner: "Jamie", Balance: 100}
	acc.Deposit(50)
	fmt.Println(acc.Balance)
}
Output
150

func (a *Account) Deposit(...) takes a pointer receiver, so a refers to the original struct rather than a copy — changes made through a inside Deposit are visible on acc after the call returns. Go automatically takes acc's address for you when you call acc.Deposit(50), even though acc itself isn't a pointer.