Anonymous structs

Anonymous structs are unnamed struct literals defined inline to create temporary data structures without declaring a new type.

The one-off structure

You are writing a function that fetches user data from an API. The response is a massive JSON object with fifty fields. You only need the id and the email. Defining a full struct type with fifty fields feels like overkill. Defining a struct with just two fields feels better, but you only use it in this one function. You don't want to clutter the package namespace with a type that serves a single purpose.

Go gives you a way to define the structure right where you need it, without a name. This is an anonymous struct. You declare the fields inline and initialize the value in the same expression. The compiler treats it as a complete type. It just lacks a name you can reference elsewhere.

What an anonymous struct actually is

An anonymous struct is a struct literal where the type definition lives inside the variable declaration or expression. The syntax starts with struct{}. Inside the braces, you list the field names and types. The compiler generates a unique type for this definition. The type exists. It has a memory layout. It has rules. It has field visibility. It simply has no identifier you can type in a function signature or a separate variable declaration.

Think of a standardized tax form versus a scribbled list. A named struct is like a printed tax form. Everyone knows what Form 1040 is. You can pass it around, store it, and fill it out anywhere. An anonymous struct is like scribbling a list on a napkin. The list has a structure. It has lines for items and prices. But that napkin is unique to this moment. If you scribble another list on a different napkin with the exact same lines, those are two different napkins. You cannot swap them in a machine that expects the first napkin.

Minimal example

The syntax mirrors a named struct declaration, but the type appears directly in the assignment.

package main

import "fmt"

func main() {
    // struct{} declares the type inline.
    // The second pair of braces initializes the value.
    s := struct {
        Name string
        Age  int
    }{
        Name: "Alice",
        Age:  30,
    }

    // Access fields like any struct.
    fmt.Println(s.Name)
}

The variable s holds a value of type struct{Name string; Age int}. You can access s.Name and s.Age. The compiler knows the layout. It allocates space for a string header and an integer. The code runs exactly like a named struct.

Anonymous structs are types. They just don't have names you can type.

How the compiler handles type identity

Go uses structural typing for interfaces, but nominal typing for everything else. This distinction matters deeply for anonymous structs. The compiler identifies types by their definition, not by their shape. Two anonymous structs with identical fields are distinct types.

package main

import "fmt"

func main() {
    a := struct{
        Name string
        Age  int
    }{Name: "Alice", Age: 30}

    b := struct{
        Name string
        Age  int
    }{Name: "Bob", Age: 25}

    // This line fails to compile.
    // a = b
}

If you uncomment the assignment, the compiler rejects the program with cannot use b (type struct{ Name string; Age int }) as type struct{ Name string; Age int } in assignment. The error message looks confusing because the types print identically. The compiler sees two different internal type IDs. Field order also matters. struct{A int; B int} is not the same type as struct{B int; A int}.

Type identity is strict. Same fields don't mean same type.

Realistic use case: selective JSON unmarshaling

The most common reason to reach for an anonymous struct is parsing JSON when you only need a subset of the data. Using map[string]interface{} works, but it forces you to perform type assertions for every field and loses compile-time safety. An anonymous struct gives you type safety and tags without polluting the namespace.

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // Simulate a JSON payload with many fields.
    payload := []byte(`{
        "id": 123,
        "email": "alice@example.com",
        "name": "Alice",
        "address": "123 Main St",
        "phone": "555-0199",
        "metadata": {"created": "2023-01-01"}
    }`)

    // Define a struct inline that only captures the fields you need.
    // Tags map JSON keys to struct fields.
    var data struct {
        ID    int    `json:"id"`
        Email string `json:"email"`
    }

    // Unmarshal directly into the anonymous struct.
    // The compiler checks that the struct is addressable.
    err := json.Unmarshal(payload, &data)
    if err != nil {
        fmt.Println("parse error:", err)
        return
    }

    // Access fields with full type safety.
    fmt.Printf("User %d: %s\n", data.ID, data.Email)
}

The json package works perfectly with anonymous structs. Tags apply normally. The unmarshaler ignores fields in the JSON that don't match the struct. This pattern keeps your code focused. You define exactly what you need, right where you use it.

Don't define a type just to use it once.

Realistic use case: composite map keys

Maps in Go require comparable keys. You cannot use slices or maps as keys. Sometimes you need a key that combines multiple values, like a host and port pair. Defining a named struct for a two-field key feels heavy. An anonymous struct provides a clean composite key.

package main

import "fmt"

func main() {
    // Create a map with a composite key.
    // The key is an anonymous struct with two string fields.
    cache := make(map[struct{
        host string
        port string
    }]bool)

    // Define the key inline for lookup.
    key := struct{
        host string
        port string
    }{host: "localhost", port: "8080"}

    cache[key] = true

    // Lookup works because the key type matches exactly.
    if cache[key] {
        fmt.Println("Connection cached")
    }
}

The struct fields are lowercase, making them private. This doesn't affect map usage, but it signals that the struct is an internal grouping mechanism. The compiler allows this because strings are comparable. If you tried to use a slice field in the key, the compiler would reject the map declaration with invalid map key type struct{ host string; data []byte }.

Anonymous structs shine when you need a temporary grouping that satisfies a type constraint, like map keys or interface satisfaction, without creating a permanent type.

Pitfalls and compiler errors

Anonymous structs introduce subtle traps if you treat them like named types.

Type mismatch in function arguments. You cannot pass an anonymous struct to a function that expects a named struct, even if the fields match.

package main

type Config struct {
    Host string
    Port int
}

func start(c Config) {
    // ...
}

func main() {
    // This fails.
    // start(struct{ Host string; Port int }{Host: "localhost", Port: 80})
}

The compiler complains with cannot use struct{ Host string; Port int } literal (type struct{ Host string; Port int }) as type Config in argument to start. You must define a variable of type Config or use a type conversion if the types were identical (which they aren't here).

Field order sensitivity. As mentioned, field order defines the type. Swapping fields creates a new type.

a := struct{ X int; Y int }{1, 2}
b := struct{ Y int; X int }{2, 1}
// a = b // Error: types differ.

Embedding interfaces. You can embed interfaces in anonymous structs. This is useful for creating quick adapters.

import "io"

// Create a struct that wraps a Reader and adds a field.
r := struct {
    io.Reader
    Name string
}{
    Reader: someReader,
    Name:   "temp",
}

This satisfies io.Reader because the embedded field provides the methods. The anonymous struct type implements the interface. This is a powerful pattern for mocking or wrapping without defining a new type.

Convention aside: gofmt aligns fields in struct literals. It also aligns the initialization values. Trust the formatter. It makes anonymous structs readable even when they span multiple lines.

When to use anonymous structs vs alternatives

Go offers several ways to group data. Choosing the right tool depends on scope, reuse, and type requirements.

Use an anonymous struct when you need a one-off grouping of values that you only use in a single expression or function. Use an anonymous struct when unmarshaling JSON and you only need a subset of fields, avoiding the overhead of a named type and the unsafe type assertions of a map. Use an anonymous struct when creating a composite map key without defining a type. Use an anonymous struct when embedding an interface to create a quick adapter or mock.

Use a named struct when the data shape appears in multiple places. Use a named struct when you need to pass the value to functions that expect a specific type. Use a named struct when the type represents a domain concept that deserves a name. Use a named struct when you need to define methods on the type.

Use multiple return values when a function returns a small, fixed set of values. Go supports returning multiple values natively. This is often cleaner than grouping them in a struct. Use a map when you need dynamic keys or don't know the fields ahead of time. Use a slice when the order matters and the elements are homogeneous.

The simplest thing that works is usually the right thing.

Where to go next