Structs
A struct groups named fields with types. Methods attach to a type via receivers; embedding gives composition without inheritance. The fundamental aggregate type in Go.
Define, methods, embedding, JSON tags
EXAMPLE
package main
import (
"encoding/json"
"fmt"
)
// 1) Basic struct
type User struct {
ID int64
Name string
Email string
}
func main() {
u := User{ID: 1, Name: "Ada", Email: "a@x.com"}
fmt.Println(u.Name)
}
// 2) Field tags — used by reflection / encoding libraries
type Product struct {
ID int64 `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
SKU string `json:"sku,omitempty"`
Created Time `json:"created_at"`
secret string `json:"-"` // omitted from JSON
}
// 3) Methods — value vs pointer receivers
type Counter struct {
n int
}
func (c Counter) Get() int { return c.n } // value receiver — operates on a copy
func (c *Counter) Inc() { c.n++ } // pointer receiver — mutates
func main() {
var c Counter
c.Inc(); c.Inc()
fmt.Println(c.Get()) // 2
}
// Rule: when methods mutate or struct is large, use pointer receivers.
// Be consistent within a type — mixing causes confusion.
// 4) Composition — embedding
type Animal struct {
Name string
}
func (a Animal) Greet() string { return "hi, I'm " + a.Name }
type Dog struct {
Animal // embedded — Dog inherits Animal's fields + methods
Breed string
}
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Border Collie"}
fmt.Println(d.Greet()) // "hi, I'm Rex" — Animal's method
fmt.Println(d.Name) // "Rex" — promoted field
}
// 5) Anonymous structs — quick, throwaway
result := struct {
Code int
Body string
}{Code: 200, Body: "ok"}
// Useful for table-driven tests
tests := []struct {
name string
input int
want int
}{
{"zero", 0, 0},
{"one", 1, 2},
{"big", 100, 200},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := double(tt.input); got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
// 6) Constructor pattern — when you need defaults / validation
type Order struct {
ID string
Items []Item
Status string
}
func NewOrder(items []Item) (*Order, error) {
if len(items) == 0 {
return nil, fmt.Errorf("order needs at least one item")
}
return &Order{
ID: newID(),
Items: items,
Status: "pending",
}, nil
}
// 7) Functional options — for many optional fields
type Server struct {
addr string
timeout time.Duration
tls bool
}
type Option func(*Server)
func WithAddr(a string) Option { return func(s *Server) { s.addr = a } }
func WithTimeout(t time.Duration) Option { return func(s *Server) { s.timeout = t } }
func WithTLS(b bool) Option { return func(s *Server) { s.tls = b } }
func NewServer(opts ...Option) *Server {
s := &Server{addr: ":8080", timeout: 5 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
srv := NewServer(WithAddr(":443"), WithTLS(true))
// 8) JSON encoding
u := User{ID: 1, Name: "Ada"}
b, _ := json.Marshal(u)
fmt.Println(string(b)) // {"ID":1,"Name":"Ada","Email":""}
// With tags:
b, _ = json.MarshalIndent(Product{ID: 1, Name: "Widget", Price: 9.99}, "", " ")
// 9) JSON decoding
var p Product
json.Unmarshal([]byte(`{"id":1,"name":"Widget","price":9.99}`), &p)
// 10) Equality + comparison
// Structs are comparable IF all their fields are comparable.
fmt.Println(u == User{ID:1, Name:"Ada", Email:"a@x.com"}) // true
// Slices, maps, funcs are NOT comparable → wraps it = compile error.
// reflect.DeepEqual for general comparison
import "reflect"
reflect.DeepEqual(o1, o2)
// 11) Empty struct — zero-byte signal
type empty struct{}
seen := make(map[string]struct{})
seen["alice"] = struct{}{}
if _, ok := seen["alice"]; ok { /* present */ }
// 12) Memory layout + alignment
// Field order matters for size. Group bigger fields first.
type Bad struct {
a bool // 1 byte + 7 padding
b int64 // 8
c bool // 1 byte + 7 padding
} // total: 24 bytes
type Good struct {
b int64 // 8
a bool // 1
c bool // 1 (no padding needed at end)
} // total: 16 bytes
// 13) Methods on non-struct types
type Email string
func (e Email) Valid() bool {
return strings.Contains(string(e), "@")
}
// 14) Best practices
// • Pointer receivers for methods that mutate or large structs
// • Constructors when invariants matter
// • Embedding > inheritance for composition
// • JSON tags on every exported field that ships over the wire
// • Avoid stutter (Order.OrderID → Order.ID)
Why it matters
Go structs + embedding + interfaces are the toolkit. Pointer receivers, JSON tags, and constructors with options — that’s 80% of Go modeling work, no inheritance hierarchy required.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
type User struct {
Name string
Age int
}
u := User{Name: "Ada", Age: 36}
fmt.Println(u.Name)
Try it Yourself »
Discussion
Loading…