Structs and Data Modeling
Overview
Structs are Go’s primary mechanism for creating custom composite types. They group related fields together.
Defining Structs
type Person struct {
Name string
Age int
Email string
Active bool
}Creating Instances
// Zero value
var p Person // All fields get zero values
// Literal (named fields - preferred)
p := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}
// Literal (positional - fragile)
p := Person{"Alice", 30, "alice@example.com", true}
// Using new
p := new(Person) // *Person with zero values
// Expression-based new (Go 1.26+)
p := new(Person{Name: "Alice"}) // *Person initialized with valuesEmbedded fields (composition)
Employee
|-- Person
| |-- Name
| |-- Age
|-- Address
| |-- City
|-- Title
Accessing Fields
p.Name = "Bob"
fmt.Println(p.Age)
// Pointer access (automatic dereference)
ptr := &p
ptr.Name = "Carol" // Same as (*ptr).NameAnonymous Structs
point := struct {
X, Y int
}{10, 20}
// Useful for one-off data
data := struct {
ID int
Value string
}{1, "test"}Embedding (Composition)
type Address struct {
Street string
City string
}
type Employee struct {
Person // Embedded
Address // Embedded
Title string
}
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Address: Address{City: "NYC"},
Title: "Engineer",
}
// Promoted fields
e.Name // Same as e.Person.Name
e.City // Same as e.Address.CityPromoted field keys in literals (Go 1.27+)
A key in a struct literal may be any valid field selector, not only a top-level field name. You can initialize a promoted embedded field without nesting another literal:
e := Employee{
Name: "Alice", // promoted from Person
Age: 30,
City: "NYC", // promoted from Address
Title: "Engineer",
}The nested form still works and is clearer when several fields of the same embedded type are set together. Ambiguous names (two embeddings that both promote Name) still require the nested form.
Comparison
Structs are comparable if all fields are comparable:
p1 := Person{Name: "Alice"}
p2 := Person{Name: "Alice"}
p1 == p2 // trueConstructors
Go uses factory functions:
func NewPerson(name string, age int) *Person {
return &Person{
Name: name,
Age: age,
Active: true, // Default
}
}
func NewPersonWithDefaults() *Person {
return &Person{
Active: true,
}
}Summary
| Pattern | Usage |
|---|---|
| Define | type Name struct { fields } |
| Create | Name{Field: value} |
| Embed | Include type name without field name |
| Tags | Field Type \key:“value”`| | Constructor |func NewType() *Type` |
More examples
Example: literal and field access
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
type User struct {
Name string
Age int
}
func main() {
u := User{Name: "ada", Age: 36}
fmt.Printf("%+v\n", u)
u.Age++
fmt.Println("age:", u.Age)
}Expected:
{Name:ada Age:36}
age: 37
Example: embedding promotes fields
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
type Address struct {
City string
}
type Person struct {
Name string
Address
}
func main() {
p := Person{Name: "lin", Address: Address{City: "Berlin"}}
fmt.Println(p.Name, "in", p.City) // promoted field
}Expected:
lin in Berlin
Runnable example
Save as main.go. From an empty directory:
go mod init example
go run .package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Address struct {
Street string
City string
}
type Person struct {
Name string
Age int
}
type Employee struct {
Person // embedded
Address // embedded
Title string
}
type User struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
Password string `json:"-"`
}
func NewEmployee(name, title, city string) *Employee {
return &Employee{
Person: Person{Name: name, Age: 0},
Address: Address{City: city},
Title: title,
}
}
func main() {
var zero Person
fmt.Printf("zero person: %+v\n", zero)
e := NewEmployee("Ada", "Engineer", "London")
e.Age = 36 // promoted field
fmt.Printf("employee: name=%s title=%s city=%s age=%d\n", e.Name, e.Title, e.City, e.Age)
// Pointer field access auto-dereferences
p := &Person{Name: "Grace", Age: 85}
p.Age++
fmt.Printf("pointer person: %+v\n", p)
// Anonymous struct for one-off data
point := struct{ X, Y int }{10, 20}
fmt.Printf("point=(%d,%d)\n", point.X, point.Y)
// Comparable when all fields are comparable
a := Person{Name: "Ada", Age: 36}
b := Person{Name: "Ada", Age: 36}
fmt.Println("a==b?", a == b)
// Tags for JSON + reflection
u := User{ID: 7, Name: "Ada", Password: "secret"}
raw, err := json.Marshal(u)
if err != nil {
fmt.Println("json error:", err)
return
}
fmt.Println("json:", string(raw))
t := reflect.TypeOf(User{})
if f, ok := t.FieldByName("Name"); ok {
fmt.Println("Name tag:", f.Tag.Get("json"))
}
if f, ok := t.FieldByName("Password"); ok {
fmt.Println("Password tag:", f.Tag.Get("json"))
}
}Expected output (illustrative):
zero person: {Name: Age:0}
employee: name=Ada title=Engineer city=London age=36
pointer person: &{Name:Grace Age:86}
point=(10,20)
a==b? true
json: {"id":7,"name":"Ada"}
Name tag: name,omitempty
Password tag: -
What to notice: - Embedding promotes fields (e.Name, e.City) without inheritance. - Factory NewEmployee sets defaults; zero Person is still valid. - JSON tags control names, omitempty, and secrets (json:"-"). - Struct equality works when every field is comparable.
Try next: Set Name to "" and remarshal—observe omitempty dropping the field.