Skip to main content

Structs

What Is a Struct?​

A struct groups fields of different types into a single composite type. Go has no classes, but you can define methods on structs to achieve an object-oriented style.

Like arrays, structs are value types. Assigning a struct or passing it to a function copies every field.


Declaring and Initialising Structs​

package main

import "fmt"

type Person struct {
Name string
Age int
Email string
}

func main() {
// 1. Named-field initialisation (recommended)
p1 := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}

// 2. Positional initialisation (depends on field order β€” avoid)
p2 := Person{"Bob", 25, "bob@example.com"}

// 3. Zero-value declaration, then field assignment
var p3 Person
p3.Name = "Carol"
p3.Age = 28

// 4. new() β€” returns a pointer
p4 := new(Person)
p4.Name = "Dave"

fmt.Println(p1, p2, p3, *p4)
}

Methods on Structs​

Methods let structs behave like objects. (Covered in depth in Ch6.)

package main

import (
"fmt"
"math"
)

type Circle struct {
X, Y float64
Radius float64
}

// Value receiver β€” read-only
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}

// Pointer receiver β€” modifies the struct
func (c *Circle) Scale(factor float64) {
c.Radius *= factor
}

func (c Circle) String() string {
return fmt.Sprintf("Circle(center=(%.1f,%.1f), r=%.1f)", c.X, c.Y, c.Radius)
}

func main() {
c := Circle{X: 0, Y: 0, Radius: 5}
fmt.Println(c)
fmt.Printf("Area: %.2f\n", c.Area())
fmt.Printf("Perimeter: %.2f\n", c.Perimeter())

c.Scale(2)
fmt.Printf("After scale: %s\n", c)
// Circle(center=(0.0,0.0), r=10.0)
}

Nested Structs​

Structs can contain other structs as fields.

package main

import "fmt"

type Address struct {
Street string
City string
Zip string
}

type Company struct {
Name string
Address Address
}

type Employee struct {
Name string
Age int
Company Company
}

func main() {
emp := Employee{
Name: "Alice",
Age: 30,
Company: Company{
Name: "Gopher Inc.",
Address: Address{
Street: "123 Main St",
City: "Seoul",
Zip: "06234",
},
},
}

fmt.Println(emp.Name)
fmt.Println(emp.Company.Name)
fmt.Println(emp.Company.Address.City)
}

Anonymous Structs​

Structs defined inline without a type name. Useful for one-off data bundles, tests, and ad-hoc JSON parsing.

package main

import (
"encoding/json"
"fmt"
)

func main() {
// Anonymous struct variable
point := struct {
X, Y int
}{X: 10, Y: 20}
fmt.Println(point) // {10 20}

// Slice of anonymous structs β€” common in table-driven tests
cases := []struct {
input int
expected string
}{
{1, "one"},
{2, "two"},
{3, "three"},
}
for _, tc := range cases {
fmt.Printf("input=%d expected=%s\n", tc.input, tc.expected)
}

// Ad-hoc JSON parsing
data := `{"name":"Alice","age":30}`
var result struct {
Name string `json:"name"`
Age int `json:"age"`
}
json.Unmarshal([]byte(data), &result)
fmt.Printf("%s is %d years old\n", result.Name, result.Age)
}

Struct Embedding​

Go uses embedding instead of inheritance. Embed a type by including it without a field name β€” its fields and methods are promoted to the outer struct.

package main

import "fmt"

type Animal struct {
Name string
}

func (a Animal) Speak() string {
return a.Name + " speaks"
}

type Dog struct {
Animal // embedded β€” no field name
Breed string
}

func (d Dog) Fetch() string {
return d.Name + " fetches the ball!" // Animal.Name promoted
}

type GuideDog struct {
Dog // Dog embeds Animal
Owner string
}

func main() {
d := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Labrador",
}

fmt.Println(d.Name) // Rex (promoted from Animal)
fmt.Println(d.Speak()) // Rex speaks (promoted from Animal)
fmt.Println(d.Fetch()) // Rex fetches the ball!

// Multi-level embedding
gd := GuideDog{
Dog: Dog{Animal: Animal{Name: "Buddy"}, Breed: "Golden"},
Owner: "Alice",
}
fmt.Println(gd.Name) // Buddy (promoted two levels)
fmt.Println(gd.Speak()) // Buddy speaks
fmt.Printf("%s owned by %s\n", gd.Name, gd.Owner)
}

Struct Tags​

Struct tags are metadata strings attached to fields. Libraries use reflection to read them for JSON serialisation, database mapping, validation, and more.

package main

import (
"encoding/json"
"fmt"
)

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // omit field when empty
Password string `json:"-"` // always exclude from JSON
Age int `json:"age,string"` // encode number as string
}

func main() {
u := User{ID: 1, Name: "Alice", Password: "secret", Age: 30}

// Struct β†’ JSON
data, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(data))
// {
// "id": 1,
// "name": "Alice",
// "age": "30"
// (Email omitted, Password excluded)
// }

// JSON β†’ Struct
jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com","age":"25"}`
var u2 User
json.Unmarshal([]byte(jsonStr), &u2)
fmt.Printf("%+v\n", u2)
}

Practical Example β€” Student Grade System​

package main

import (
"fmt"
"sort"
)

type Score struct {
Subject string
Point int
}

type Student struct {
ID string
Name string
Scores []Score
}

func (s Student) Total() int {
total := 0
for _, sc := range s.Scores {
total += sc.Point
}
return total
}

func (s Student) Average() float64 {
if len(s.Scores) == 0 {
return 0
}
return float64(s.Total()) / float64(len(s.Scores))
}

func (s Student) Grade() string {
avg := s.Average()
switch {
case avg >= 90:
return "A"
case avg >= 80:
return "B"
case avg >= 70:
return "C"
default:
return "F"
}
}

type Classroom struct {
Students []Student
}

func (c *Classroom) Add(s Student) {
c.Students = append(c.Students, s)
}

func (c Classroom) RankByAverage() []Student {
sorted := make([]Student, len(c.Students))
copy(sorted, c.Students)
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Average() > sorted[j].Average()
})
return sorted
}

func main() {
classroom := Classroom{}
classroom.Add(Student{
ID: "s001", Name: "Alice",
Scores: []Score{{"Math", 95}, {"English", 88}, {"Science", 92}},
})
classroom.Add(Student{
ID: "s002", Name: "Bob",
Scores: []Score{{"Math", 72}, {"English", 80}, {"Science", 68}},
})
classroom.Add(Student{
ID: "s003", Name: "Carol",
Scores: []Score{{"Math", 85}, {"English", 90}, {"Science", 88}},
})

fmt.Println("=== Grade Rankings ===")
for rank, s := range classroom.RankByAverage() {
fmt.Printf("%d. [%s] %s β€” avg: %.1f (%s)\n",
rank+1, s.ID, s.Name, s.Average(), s.Grade())
}
}