Understanding Structs in Go: The Foundation of Data Modeling
In Go, there are no classes like in Java or Python. Instead, Go provides something more lightweight but equally powerful — structs. Structs let you define your own types that group related data together. If you’re building real-world Go applications, you’ll use structs constantly.
Let’s walk through what structs are, how to use them, and how they help you organize data cleanly.
Why Use Structs?
Imagine you’re building a blogging platform and want to represent a blog post. Without structs, you’d manage individual variables like this:
title := "Go Structs Guide"
author := "Aditya"
content := "This blog explains structs in Go"
Managing 100 blog posts like this quickly becomes chaos. Structs let you define a reusable blueprint:
type BlogPost struct {
Title string
Author string
Content string
}
Now you can work with a BlogPost as a cohesive unit.
How to Create a Struct Instance
Once you’ve defined your struct, create a new blog post like this:
post := BlogPost{
Title: "Go Structs Guide",
Author: "Aditya",
Content: "This blog explains structs in Go",
}
Access individual fields with dot notation:
fmt.Println(post.Title) // Output: Go Structs Guide
Compact Syntax (Not Recommended for Beginners)
Go also allows positional initialization, but it’s error-prone when you have many fields:
post := BlogPost{"Short Title", "Author Name", "Content here"}
Stick to named fields for clarity.
Default Values and Zero Initialization
If you declare a struct without assigning values, all fields initialize to their zero values automatically:
var draft BlogPost
fmt.Println(draft) // Output: { } (empty strings)
This is useful when building up an object incrementally.
Passing Structs to Functions
Pass a struct to a function like any other variable:
func PrintPost(post BlogPost) {
fmt.Println("Title:", post.Title)
}
PrintPost(post)
This passes a copy of the struct. Changes inside the function don’t affect the original.
Working with Pointers to Modify Structs
To modify a struct inside a function, pass a pointer:
func UpdateAuthor(post *BlogPost, newAuthor string) {
post.Author = newAuthor
}
UpdateAuthor(&post, "New Author")
Now post.Author reflects the change in the original struct.
Methods on Structs
Go lets you attach functions (methods) to struct types. A method with a value receiver doesn’t modify the original:
func (p BlogPost) Summary() string {
return p.Title + " by " + p.Author
}
fmt.Println(post.Summary())
Methods That Modify the Struct
To change values inside a method, use a pointer receiver:
func (p *BlogPost) UpdateContent(newContent string) {
p.Content = newContent
}
post.UpdateContent("Updated blog post content")
This directly modifies post.Content.
Constructor Functions
Go doesn’t have built-in constructors, but you can create factory functions to initialize structs:
func NewBlogPost(title, author, content string) BlogPost {
return BlogPost{Title: title, Author: author, Content: content}
}
post := NewBlogPost("Hello", "Aditya", "Content")
Adding Validation
Constructor functions are the perfect place to enforce business rules:
func NewBlogPost(title, author, content string) (BlogPost, error) {
if title == "" {
return BlogPost{}, fmt.Errorf("title cannot be empty")
}
return BlogPost{Title: title, Author: author, Content: content}, nil
}
This ensures you never create an invalid BlogPost.
Structs vs Classes: Key Differences
| Concept | Go Structs | Classes (Java/Python) |
|---|---|---|
| Inheritance | Embedding (composition) | Class hierarchy |
| Constructors | Factory functions | new / __init__ |
| Methods | Value or pointer receivers | Instance methods |
| Access control | Exported (uppercase) vs unexported | public / private |
Key Takeaways
- Structs are Go’s primary tool for grouping related data into a named type.
- Use value receivers for read-only methods and pointer receivers when you need to modify the struct.
- Constructor functions prevent invalid state and reduce boilerplate.
- Go favors composition over inheritance — embed structs instead of extending classes.
In Part 2, we’ll dive into exporting struct fields across packages, embedding structs, and Go’s approach to interface-based polymorphism.