Struct Basics in Go

Learn what structs are in Go, how to declare struct types, create struct literals with different initialization styles, and understand zero-value behavior for safe, predictable code.

A struct is Go's way of letting you create your own data types by bundling together named values that belong together. If you have ever filled out a form with fields for a name, an address, and a phone number, you already have the right mental model: a struct is that form, and each field holds one piece of the larger whole. Rather than juggling separate variables that are only meaningful as a group, you define a struct that carries them all as a single value you can pass around, store, and reason about as one thing.

Go's structs are deliberately simple. They do not support inheritance, and they do not automatically generate methods the way classes do in other languages. What they give you is a clean, explicit container for related data with predictable behavior — nothing hidden, nothing magical.

Struct Type Declarations

Before you can use a struct, you first describe its shape. The type keyword followed by a name, the keyword struct, and a set of fields inside curly braces declares a new named struct type.

type Person struct {
    FirstName string
    LastName  string
    Age       int
}

This declaration creates a type called Person that has three fields: FirstName and LastName of type string, and Age of type int. The compiler now understands what a Person is, and you can declare variables of that type.

When multiple consecutive fields share the same type, you can write them on one line to reduce repetition:

type Point struct {
    X, Y float64
}

X and Y are both float64, but they remain independent fields you can access separately.

Structs vs. classes:

If you are coming from a language with classes, you can think of a struct as a class that only has fields — no methods, no inheritance, no constructors. Methods get attached separately via receiver functions (covered in methods), and code reuse happens through embedding, not subclassing.

The declaration alone does not allocate any memory or create a value. It only registers the blueprint. Every field must have an explicit type, and you cannot mix field declarations with method definitions inside the struct body — methods live outside, in their own function declarations.

Struct Literals

Once you have a struct type, you can create concrete values — individual people, points, or whatever your type represents — using struct literals. A literal is a written-out expression that produces a struct value, and Go gives you several ways to write them depending on whether you value brevity, clarity, or partial initialization.

Positional initialization

The most compact form lists values in the order the fields were declared, separated by commas.

p := Person{"Alice", "Smith", 30}
fmt.Println(p)
// Output: {Alice Smith 30}

This works because the first string matches FirstName, the second matches LastName, and the int matches Age. The compiler checks that the number and types of values line up with the declaration, but it does not remind you which position corresponds to which field.

Order-dependent literals are fragile:

If you later add a new field to the struct — say, an Email string before Age — every positional literal in your codebase will break or, worse, silently assign wrong data to the wrong fields. For maintainability, use positional literals only in small, local contexts where the struct definition is unlikely to change, like table-driven tests.

Positional literals also force you to provide a value for every field. There is no syntax to leave one out; if you omit a value, the compiler rejects the code. This is by design — Go wants you to be explicit when you are skipping the named-field safety net.

Named-field initialization

The more common and resilient style names each field you want to set. Field order does not matter, and any field you leave out automatically receives its zero value.

p := Person{
    FirstName: "Bob",
    LastName:  "Jones",
    Age:       42,
}
fmt.Println(p)
// Output: {Bob Jones 42}

This syntax makes it obvious what each value represents, even to someone who has never seen the struct definition. It also lets you initialize only a subset of fields, with the rest falling back to sensible defaults (zero values).

p := Person{
    FirstName: "Carol",
}
fmt.Println(p)
// Output: {Carol  0}

LastName is an empty string and Age is 0 — both are the zero values for their respective types. This behavior is the same whether you leave the field out or explicitly set it to the zero value; there is no difference between "unset" and "set to zero" in a struct literal.

Pointer literals

You can obtain a pointer to a struct directly from a literal by prefixing the literal with &.

p := &Person{
    FirstName: "Dan",
    LastName:  "Evans",
    Age:       28,
}
fmt.Println(p)
// Output: &{Dan Evans 28}

The expression &Person{...} allocates a new Person, populates its fields, and returns the address. Go's garbage collector tracks the allocation, so you can safely return this pointer from a function without worrying about the memory becoming invalid.

func NewPerson(first, last string, age int) *Person {
    return &Person{
        FirstName: first,
        LastName:  last,
        Age:       age,
    }
}

This constructor-function pattern is idiomatic Go. It hides the allocation details behind a descriptive name and lets you add validation or default logic later without changing all the call sites.

Constructor functions are a recognized Go idiom:

When you return a *Person from a function called NewPerson, your peers instantly know they are getting a newly constructed value with a pointer. Many standard-library packages follow this convention.

Zero Value of Structs

Every struct type has a zero value — the value a variable of that type takes on when you declare it without an explicit initializer. The zero value of a struct is not nil; it is a struct where every field holds its own type's zero value (empty string, 0, false, nil pointer, etc.).

var p Person
fmt.Printf("%+v\n", p)
// Output: {FirstName: LastName: Age:0}

This output shows that all three fields are set to their zero state: empty strings for the two name fields, and 0 for the integer. The struct itself is a valid, usable value — you can read its fields, pass it to functions, and even call methods on it if any exist.

Zero-value usefulness:

Because the zero value is always fully initialized and never nil, many Go types are designed so that a zero-value instance works meaningfully right away. For example, a sync.Mutex is ready to use without any explicit setup.

The zero value appears in several situations beyond explicit var declarations:

  • Fields omitted from a named literal
  • Uninitialized elements of a new slice or array of structs
  • The value returned when you access a map key that does not exist

All of these produce a fully initialized, zeroed-out struct rather than a panic or a null reference. This consistency removes an entire category of null-pointer-style bugs that plague other languages.

Do not compare a zero struct to nil:

A struct variable is never nil. Only pointers, slices, maps, channels, functions, and interfaces can be nil. If you write if p == nil, the compiler will reject the code with a type-mismatch error.

Structs that contain pointers or reference types still have zero values — the pointer field itself is nil, but the overall struct is valid. That means you can always safely read any field of a zero-value struct; you only need to guard against dereferencing individual fields that might be nil.

Nil reference fields in a zero struct:

If your struct has a *int field, its zero value is nil. Accessing *p.Age on a zero-value struct will compile but panic at runtime. Always check pointer fields before dereferencing them.

Knowing what zero values look like for your own types helps you decide which fields need explicit initialization and which can sensibly default to zero. When a zero value is useful on its own, users of your type can start working with it immediately, reducing ceremony.