Define Go structs, attach methods, use embedding for composition, and understand value vs pointer receivers.
Time
30 minutes
Prerequisites
Lab 01–03
Tools
Docker image: zchencow/innozverse-go:latest
Lab Instructions
Step 1: Struct Basics
dockerrun--rmzchencow/innozverse-go:latestgorun-<<'EOF'package mainimport "fmt"type Address struct { Street string City string State string Zip string}type Person struct { FirstName string LastName string Age int Email string Address Address // embedded struct (by value)}func (p Person) FullName() string { return p.FirstName + " " + p.LastName}func (p Person) String() string { return fmt.Sprintf("%s (%d) <%s>", p.FullName(), p.Age, p.Email)}func NewPerson(first, last string, age int, email string) Person { return Person{FirstName: first, LastName: last, Age: age, Email: email}}func main() { // Literal initialization p := Person{ FirstName: "Chen", LastName: "Dr.", Age: 42, Email: "[email protected]", Address: Address{ Street: "950 Ridge Rd", City: "Claymont", State: "DE", Zip: "19703", }, } fmt.Println(p) fmt.Println("City:", p.Address.City) // Struct comparison — structs with comparable fields are comparable p2 := NewPerson("Chen", "Dr.", 42, "[email protected]") fmt.Println("Equal (no address):", p.FirstName == p2.FirstName && p.Age == p2.Age) // Anonymous struct point := struct{ X, Y int }{X: 10, Y: 20} fmt.Println("Point:", point) // Struct tags (used by JSON, ORM, etc.) type Product struct { ID int `json:"id" db:"product_id"` Name string `json:"name"` Price float64 `json:"price,omitempty"` } prod := Product{ID: 1, Name: "Surface Pro", Price: 864} fmt.Printf("Product: %+v\n", prod)}EOF
💡 Struct tags (json:"name") are metadata strings read by reflection packages. They control how encoding/json, GORM, and validator work. Tags don't affect runtime behavior unless a package explicitly reads them via reflect.StructTag. The +v format verb prints field names alongside values.
📸 Verified Output:
Step 2: Value vs Pointer Receivers
💡 Use pointer receivers when: (1) the method modifies the struct, OR (2) the struct is large (copying is expensive), OR (3) you need consistency — if any method uses a pointer receiver, all methods should. Value receivers are safe for small, immutable data like Point. Go auto-dereferences: p.Method() works even if p is a pointer.
📸 Verified Output:
Step 3: Embedding & Composition
💡 Embedding is Go's answer to inheritance. When you embed Named in Product, all of Named's fields and methods are promoted to Product — you can call p.Name instead of p.Named.Name. But it's composition, not inheritance — Product is not a Named. This avoids the fragile base class problem.
📸 Verified Output:
Steps 4–8: Constructors, Stringer, Builder, Repository, Capstone Inventory System