Structs and Methods
## Learning Objectives
- Define and use structs
- Create and initialize struct instances
- Work with struct fields
- Define methods with receivers
- Understand value vs pointer receivers
- Embed structs for composition
## Structs
### Definition
```go
type Person struct {
Name string
Age int
City string
}
```
### Declaration
```go
var p Person // Zero-initialized
p := Person{} // Also zero-initialized
p := Person{Name: "Alice", Age: 30} // With field names
p := Person{"Alice", 30, "NYC"} // Positional (brittle)
```
### Field Access
```go
p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name) // Alice
fmt.Println(p.Age) // 30
p.Age = 31 // Modify field
fmt.Println(p.Age) // 31
```
## Creating Instances
### Zero Value
```go
var p Person // All fields zero-valued
fmt.Println(p) // { 0 }
```
### Composite Literal
```go
p := Person{
Name: "Alice",
Age: 30,
}
```
### new Operator
```go
p := new(Person) // Returns *Person (pointer)
p.Name = "Bob" // Arrow syntax not needed
fmt.Println(p) // &{Bob 0}
```
### Pointer to Literal
```go
p := &Person{Name: "Carol", Age: 25}
fmt.Println(p) // &{Carol 25}
```
## Struct with Pointers
```go
type Node struct {
Value int
Next *Node
}
n1 := &Node{Value: 1}
n2 := &Node{Value: 2}
n1.Next = n2
fmt.Println(n1.Next.Value) // 2
```
## Methods
### Value Receiver
```go
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func main() {
r := Rectangle{Width: 10, Height: 5}
fmt.Println(r.Area()) // 50
}
```
### Pointer Receiver
```go
type Rectangle struct {
Width, Height float64
}
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
r := Rectangle{Width: 10, Height: 5}
r.Scale(2)
fmt.Println(r.Width, r.Height) // 20 10
}
```
### When to Use Pointers
| Situation | Recommendation |
|-----------|----------------|
| Modify the receiver | Use `*T` |
| Mutate a large object | Use `*T` |
| Ensure consistency | Use `*T` |
| Read-only method | Use `T` or `*T` |
## Named Types
### Type Definition
```go
type Celsius float64
type Fahrenheit float64
func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func main() {
var c Celsius = 100
fmt.Printf("%.2f C = %.2f F\n", c, c.ToFahrenheit())
}
```
## Embedded Structs (Composition)
### Basic Embedding
```go
type Address struct {
Street string
City string
State string
}
type Person struct {
Name string
Address // Embedded (no field name)
}
func main() {
p := Person{
Name: "Alice",
Address: Address{
Street: "123 Main St",
City: "NYC",
State: "NY",
},
}
fmt.Println(p.Street) // Direct access (promoted)
fmt.Println(p.Address.Street) // Also works
}
```
### Shadowing
```go
type A struct {
x int
}
type B struct {
A
x int // Shadows A.x
}
func main() {
b := B{A: A{x: 1}, x: 2}
fmt.Println(b.x) // 2 (B's x)
fmt.Println(b.A.x) // 1 (A's x)
}
```
## Struct Tags
### Struct Tag Definition
```go
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
```
### Common Tags
```go
type Person struct {
Name string `json:"name" xml:"name"`
Age int `json:"age,omitempty"`
}
```
### Accessing Tags
```go
import "reflect"
t := reflect.TypeOf(Person{})
field, _ := t.FieldByName("Name")
fmt.Println(field.Tag.Get("json")) // name
```
## Comparing Structs
### Comparable
Structs are comparable if all fields are comparable.
```go
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
if p1 == p2 {
fmt.Println("Equal")
}
```
### With Non-comparable Fields
```go
type Person struct {
Name string
Slice []int // Not comparable
}
p1 := Person{Name: "Alice"}
p2 := Person{Name: "Alice"}
// p1 == p2 // COMPILE ERROR
```
## Function vs Method
### Function
```go
func area(r Rectangle) float64 {
return r.Width * r.Height
}
```
### Method
```go
func (r Rectangle) area() float64 {
return r.Width * r.Height
}
```
## Memory Layout
### Padding
```go
type Foo struct {
A int8 // 1 byte
_ [7]byte // padding
B int64 // 8 bytes
}
type Bar struct {
B int64 // 8 bytes
A int8 // 1 byte
_ [7]byte // padding
}
```
### Order Matters
Put larger fields first to minimize padding:
```go
type Efficient struct {
B int64 // 8 bytes
A int8 // 1 byte
C int32 // 4 bytes
}
```
## Common Patterns
### Constructor Pattern
```go
type Stack struct {
items []int
}
func NewStack() *Stack {
return &Stack{items: make([]int, 0)}
}
func (s *Stack) Push(item int) {
s.items = append(s.items, item)
}
```
### Option Pattern
```go
type Server struct {
Port int
Timeout int
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.Port = port
}
}
func NewServer(opts ...Option) *Server {
s := &Server{Port: 8080, Timeout: 30}
for _, opt := range opts {
opt(s)
}
return s
}
func main() {
s := NewServer(WithPort(9090))
}
```
## Summary
- Structs group related fields together
- Use composite literals to initialize: `Person{Name: "Alice"}`
- `new(Type)` returns a pointer to zero-initialized struct
- Methods have receivers - use pointer receiver to modify
- Embedded structs provide composition (no inheritance)
- Struct tags annotate fields for encoding/decoding
- Struct field order affects memory layout
- Use constructor functions for complex initialization
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →