Go Structures Tutorial
Structures (structs) are the fundamental building blocks for organizing and working with complex data in Go. Unlike classical object-oriented languages, Go uses structs to create custom types that group together related data fields. This tutorial will guide you through all aspects of working with structs in Go.
1. Struct Basics
1.1 What Are Structs?
Structs are composite data types that allow you to combine fields of different types under a single name. They are similar to classes in other languages but are more lightweight and don't support inheritance.
Key characteristics of structs:
- Group related data together
- Can contain fields of any type (including other structs)
- Value types (copied when assigned or passed to functions)
- Support methods (like class methods in OOP)
1.2 Defining Structs
Struct definitions specify the name and type of each field. The basic syntax is:
type StructName struct {
Field1 Type1
Field2 Type2
// ...
}
Example with a Person struct:
type Person struct {
FirstName string
LastName string
Age int
Height float64
}
1.3 Creating Struct Instances
There are several ways to create struct instances in Go:
1. Zero-value initialization
Creates a struct with all fields set to their zero values:
var p Person // All fields get zero values
2. Literal initialization (positional)
Initialize fields in order (must include all fields):
p := Person{"John", "Doe", 30, 1.75}
3. Literal initialization (named fields)
More readable and flexible approach:
p := Person{
FirstName: "Jane",
Age: 25,
Height: 1.68,
// LastName omitted (gets zero value)
}
4. Using new()
Allocates memory and returns a pointer:
pPtr := new(Person)
pPtr.FirstName = "Mike" // Access fields through pointer
1.4 Accessing Struct Fields
Struct fields are accessed using dot notation, whether working with the struct directly or through a pointer:
// Direct access
person := Person{"John", "Doe", 30, 1.75}
fmt.Println(person.FirstName) // "John"
// Pointer access (automatic dereferencing)
personPtr := &person
fmt.Println(personPtr.LastName) // "Doe" - no need for (*personPtr).LastName
2. Struct Methods
2.1 Understanding Methods
Methods in Go are functions that operate on a specific type (the receiver). Struct methods allow you to define behavior associated with your data.
Key points about methods:
- Defined with a special receiver parameter before the function name
- Can be value receivers (operate on a copy) or pointer receivers (operate on original)
- Support the same features as regular functions
2.2 Value Receivers
Value receivers work on a copy of the struct. Use these when:
- The method doesn't need to modify the receiver
- You want to work with an immutable copy
- The struct is small (copying is efficient)
// Value receiver example
func (p Person) FullName() string {
return p.FirstName + " " + p.LastName
}
// Usage
person := Person{"John", "Doe", 30, 1.75}
name := person.FullName() // Works on a copy of person
2.3 Pointer Receivers
Pointer receivers work on the original struct. Use these when:
- The method needs to modify the receiver
- The struct is large (avoid copying overhead)
- You want consistent behavior (if some methods need pointers, use for all)
// Pointer receiver example
func (p *Person) Birthday() {
p.Age++ // Modifies the original struct
}
// Usage
person := Person{"John", "Doe", 30, 1.75}
person.Birthday() // Age is now 31
2.4 Method Selection Guidelines
Choosing between value and pointer receivers:
| Scenario | Recommended Receiver |
|---|---|
| Method needs to modify receiver | Pointer |
| Struct contains sync.Mutex | Pointer |
| Struct is large (> 64 bytes) | Pointer |
| Method only reads data | Value |
| Small struct (< 64 bytes) | Either (value often preferred) |
3. Composition and Embedding
3.1 Struct Embedding
Go favors composition over inheritance. Struct embedding allows one struct to include another struct's fields and methods.
type ContactInfo struct {
Email string
Phone string
}
type Employee struct {
Name string
ContactInfo // Embedded struct (no field name)
}
// Usage
emp := Employee{
Name: "John Doe",
ContactInfo: ContactInfo{
Email: "[email protected]",
Phone: "123-456-7890",
},
}
// Fields are promoted
fmt.Println(emp.Email) // Works directly
3.2 Method Promotion
When you embed a struct, its methods are also promoted to the containing struct:
type Writer struct {
Name string
}
func (w Writer) Write(message string) {
fmt.Printf("%s writes: %s\n", w.Name, message)
}
type Author struct {
Writer // Embed Writer
Book string
}
// Usage
author := Author{
Writer: Writer{Name: "Jane Austen"},
Book: "Pride and Prejudice",
}
author.Write("It is a truth universally acknowledged...")
// Output: "Jane Austen writes: It is a truth universally acknowledged..."
3.3 Overriding Embedded Methods
You can override methods from embedded types by defining a method with the same name:
func (a Author) Write(message string) {
fmt.Printf("%s, author of %s, writes: %s\n",
a.Name, a.Book, message)
}
// Now calling Write() will use Author's version
author.Write("New novel coming soon")
4. Advanced Struct Techniques
4.1 Field Tags
Struct tags are string literals that provide metadata about struct fields, commonly used for JSON/XML serialization and validation.
type User struct {
ID int `json:"id" db:"user_id"`
Username string `json:"username" validate:"required,min=3"`
Password string `json:"-"` // Skip in JSON
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
// Usage with JSON
user := User{
ID: 1,
Username: "admin",
Password: "secret",
CreatedAt: time.Now(),
}
jsonData, _ := json.Marshal(user)
// {"id":1,"username":"admin","created_at":"2023-07-20T12:00:00Z"}
4.2 Anonymous Structs
Go supports anonymous structs - structs defined without a type name, useful for one-time use cases.
// Inline struct definition and initialization
config := struct {
Timeout time.Duration
Retries int
}{
Timeout: 5 * time.Second,
Retries: 3,
}
// Common in tests
testCases := []struct {
input string
expected int
hasError bool
}{
{"123", 123, false},
{"abc", 0, true},
}
4.3 Struct Comparison
Structs can be compared if all their fields are comparable. Pointer fields are compared by address, not value.
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{2, 3}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
// For structs with non-comparable fields (like slices),
// use reflect.DeepEqual()
5. Memory Layout and Performance
5.1 Struct Field Ordering
Field order affects memory usage due to padding. Proper ordering can reduce struct size.
// Bad ordering - wastes space (24 bytes)
type Inefficient struct {
a bool // 1 byte
b int64 // 8 bytes
c bool // 1 byte
// 6 bytes padding
}
// Better ordering - only 16 bytes
type Efficient struct {
b int64 // 8 bytes
a bool // 1 byte
c bool // 1 byte
// 6 bytes padding can be used for other fields
}
5.2 Pointer vs Value Semantics
Understanding when to use pointers to structs versus direct values:
| Use Case | Recommendation |
|---|---|
| Function parameters | Pointer for large structs or when modification needed |
| Return values | Pointer for large structs, value for small |
| Collections (slices, maps) | Pointers if structs are large or need modification |
| Concurrent access | Pointers with proper synchronization |
6. Common Struct Patterns
6.1 Constructor Functions
Idiomatic way to initialize structs with validation:
func NewPerson(name string, age int) (*Person, error) {
if name == "" {
return nil, errors.New("name cannot be empty")
}
if age <= 0 {
return nil, errors.New("age must be positive")
}
return &Person{Name: name, Age: age}, nil
}
// Usage
person, err := NewPerson("Alice", 25)
if err != nil {
// handle error
}
6.2 Builder Pattern
For complex struct initialization with many optional fields:
type Config struct {
Timeout time.Duration
Retries int
Logger io.Writer
}
type ConfigBuilder struct {
config Config
}
func (b *ConfigBuilder) WithTimeout(timeout time.Duration) *ConfigBuilder {
b.config.Timeout = timeout
return b
}
func (b *ConfigBuilder) Build() Config {
// Set defaults and validate
if b.config.Timeout == 0 {
b.config.Timeout = 5 * time.Second
}
return b.config
}
// Usage
cfg := ConfigBuilder{}.
WithTimeout(10 * time.Second).
Build()