Skip to main content

Structs

In the Go programming language, structs are the bedrock of data organization. They are the primary way we group related fields together to form meaningful, complex data types. Unlike traditional object-oriented languages that rely on class inheritance, Go uses structs coupled with embedding to achieve powerful composition.

This guide will walk you through defining, initializing, and using embedding to build flexible Go programs.


1. Defining the Blueprint: What is a Struct?

A struct (short for structure) is a user-defined type that allows you to combine items of possibly different types into a single unit. Think of it as a template or a blueprint for creating records.

Basic Struct Definition Syntax

We define a struct using the type keyword, followed by the struct name, and the struct keyword.

package main

// Defines a struct named 'Rectangle'
type Rectangle struct {
Width float64
Height float64
Color string
}

In the example above, Rectangle is a new type that contains three fields: two float64 values (Width and Height) and one string (Color).


2. Bringing Data to Life: Struct Literals

Once you define a struct, you need a way to create an instance of it and populate its fields. This is done using a struct literal.

The most robust way to initialize a struct is by explicitly listing the field names and their corresponding values. This method is highly recommended because the code remains correct even if the order of fields in the struct definition changes.

func main() {
// 1. Keyed initialization
r1 := Rectangle{
Width: 10.5,
Height: 5.0,
Color: "Blue",
}

fmt.Println(r1.Color) // Output: Blue

// You can initialize only some fields; others default to zero values
r2 := Rectangle{
Width: 20.0,
}
// r2.Height will be 0.0, r2.Color will be "" (empty string)
}

Initialization Method 2: Positional Fields (Avoid)

You can initialize a struct by listing values in the exact order they appear in the definition, but this is brittle and generally discouraged for non-trivial structs.

// This only works if you remember the exact order: Width, Height, Color
r3 := Rectangle{15.0, 7.5, "Red"}

Insight: Always use keyed initialization in production code. It enhances readability and protects you from bugs when refactoring struct definitions.


3. Go’s Secret Sauce: Struct Embedding for Composition

A key difference between Go and languages like Java or C++ is the absence of classical inheritance. Instead, Go uses struct embedding to achieve composition and reuse behavior.

Embedding involves placing one struct inside another. The outer struct implicitly gains all the fields and methods of the embedded struct.

Step-by-Step Embedding Example

Step 1: Define the Base Struct

type Person struct {
FirstName string
LastName string
Age int
}

Step 2: Define the Composing Struct and Embed Instead of redefining FirstName, LastName, and Age, we simply embed the Person struct.

type Employee struct {
// Embedding the Person struct
Person

EmployeeID int
Department string
}

Step 3: Accessing Embedded Fields When you create an Employee, you can access the fields of the embedded Person directly. This is called field promotion.

func main() {
dev := Employee{
Person: Person{
FirstName: "Alice",
LastName: "Smith",
Age: 35,
},
EmployeeID: 4001,
Department: "Engineering",
}

// Accessing embedded fields directly:
fmt.Println(dev.FirstName) // Output: Alice

// You can also access them via the struct name:
fmt.Println(dev.Person.LastName) // Output: Smith
}

Why is this powerful? Embedding allows us to build complex objects by combining smaller, simple ones. If we add a method to the Person struct (e.g., Greet()), the Employee struct automatically "promotes" and gains access to that method.


4. Using Anonymous Fields

In the embedding example above, we used Person as the field name, which matched the type name. When a field is defined only by its type, it is called an anonymous field.

Anonymous fields are most commonly used in two scenarios:

  1. Struct Embedding: As shown above, where the outer struct inherits fields and methods.
  2. Simple Value Groups: Defining a struct where the field name doesn't matter, just the value type.

Example of a Non-Embedded Anonymous Field

type Item struct {
string // Anonymous field of type string
int // Anonymous field of type int
}

func main() {
item := Item{
string: "Laptop",
int: 1200,
}

fmt.Println("Name:", item.string) // Output: Laptop
fmt.Println("Price:", item.int) // Output: 1200
}

Note: Using anonymous fields of basic types (like string or int) should be done sparingly, as it can confuse readers.


Summary of Struct Concepts

ConceptPurposeKey Syntax
Struct DefinitionDefine a custom data type to group related fields.type MyStruct struct { ... }
Struct LiteralInstantiate and initialize a struct instance.s := MyStruct{Field: Value}
Struct EmbeddingAchieve composition by including one struct inside another.type Outer struct { Inner; ... }
Anonymous FieldA field where the name is omitted, defaulting to the type name.type Example struct { string }

By mastering these fundamental concepts, you are ready to model complex data structures effectively and write idiomatic Go code.