Go July 1, 2025 Aditya Rawas

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

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

ConceptGo StructsClasses (Java/Python)
InheritanceEmbedding (composition)Class hierarchy
ConstructorsFactory functionsnew / __init__
MethodsValue or pointer receiversInstance methods
Access controlExported (uppercase) vs unexportedpublic / private

Key Takeaways

In Part 2, we’ll dive into exporting struct fields across packages, embedding structs, and Go’s approach to interface-based polymorphism.