A struct in Go is a composite type that groups multiple fields under a single name. It is the basic building block for representing entities with their own data.
If you come from an object-oriented language like Java, C# or C++, you are probably looking for the class keyword.
In Go there are no classes. Go groups data into structs and associates behavior through methods.
Let’s see how to define our own composite types and how to use composition instead of a class hierarchy.
What is a struct
A struct is a collection of typed fields. It is the way we have in Go to create types that group related information.
Think of a Struct as a blueprint for an object, or like a “DTO” (Data Transfer Object) in other languages.
Definition
Use the type keyword, followed by the name, the struct keyword, and curly braces.
package main
import "fmt"
// Define a new type called 'Player'
type Player struct {
Name string
Level int
Class string
Alive bool
}Remember the visibility rule: if the name of the struct or one of its fields starts with an uppercase letter, it is exported. If it starts with a lowercase letter, it is only accessible from within its package.
Creating values of a struct
Once we have defined the “blueprint”, we can create instances in several ways. Go is very flexible here.
Declaration with zero values
If we declare the variable without assigning anything, Go fills all fields with their Zero Value.
var p1 Player
// p1.Name is ""
// p1.Level is 0
// p1.Alive is falseStruct literal
We can initialize the values when creating them.
// Assignment with names (Recommended)
p2 := Player{
Name: "Link",
Level: 10,
Class: "Swordsman",
Alive: true,
}
// Positional assignment (Not recommended)
// It is fragile: if you change the order of the fields in the struct, this breaks.
p3 := Player{"Zelda", 99, "Mage", true}Using new
The function new(T) allocates memory, zeroes everything out, and returns a pointer (*T).
p4 := new(Player) // p4 is of type *Player
p4.Name = "Ganondorf"Accessing fields, also with pointers
To access the data we use the dot ..
If we have a pointer to a struct, we don’t need to manually dereference it to select a field. Go supports the abbreviated form.
p := Player{Name: "Mario"}
ptr := &p
// In C/C++ you would have to do: (*ptr).Name
// In Go it works directly:
fmt.Println(ptr.Name) // Prints "Mario"
ptr.Level = 5 // Modifies the originalEmbedded fields and composition
In Go there is no inheritance (extends). You cannot say that Car inherits from Vehicle.
Instead, Go bets everything on Composition (Has-a instead of Is-a).
To facilitate this, Go has embedded fields (embedding).
If we declare a field using only its type, its fields and methods can be promoted to the outer type. There are no parent and child types: the embedded field still exists and we can select it explicitly.
type Engine struct {
Horsepower int
Type string
}
type Car struct {
Brand string
Engine // <--- Anonymous field (Embedding)
}
func main() {
myCar := Car{
Brand: "Tesla",
Engine: Engine{
Horsepower: 500,
Type: "Electric",
},
}
// DIRECT ACCESS (Field Promotion)
// We can access Horsepower as if it were a field of Car
fmt.Println(myCar.Horsepower) // 500
// We can also be explicit if we want
fmt.Println(myCar.Engine.Horsepower) // 500
}Promotion and collisions
Promotion allows using myCar.Start() if Engine has that method, even though Car and Engine are still distinct types.
If there is a name collision (for example, Car and Engine have a Name field), Go forces you to use the full path (myCar.Engine.Name) to resolve the ambiguity.
Constructors in Go
You are probably wondering: Where is the constructor?
Go does not have special constructors like public Player() {...}.
When we need to control initialization, the convention is to create a normal function called NewType. It can return a value or a pointer, and if the construction can fail, also an error.
// Constructor function
func NewPlayer(name string) *Player {
return &Player{
Name: name,
Level: 1, // Sensible default values
Alive: true,
}
}
func main() {
p := NewPlayer("Luigi")
}Using these “factory” functions is very useful when the struct initialization requires complex logic or validations, or to ensure that certain private fields are initialized correctly.