iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…