10.3 Anonymous Struct Types
Right, anonymous structs. You’ve probably seen these little weirdos out in the wild and maybe scratched your head. They look like a struct got lost and forgot to declare itself. And you’d be right. An anonymous struct is exactly that: a struct type defined without a name, usually declared and used in the same place. It’s the ultimate “use it and lose it” data structure.
We use them for two main reasons: when we need a one-off, highly localized data container, or when we’re dealing with something like JSON unmarshaling and we want to pluck a few specific fields out of a giant blob without defining a whole new named type. They’re convenient, but like most convenient things (see: fast food, duct tape), they come with caveats.
The Basic Syntax: No Name, No Problem
The syntax is straightforward. You just… leave the name out. You declare it and initialize it in one go, usually with a struct literal.
// Declaring and initializing an anonymous struct
data := struct {
ID int
Name string
IsAdmin bool
}{
ID: 1,
Name: "Alice",
IsAdmin: true,
}
fmt.Printf("Hello, %s (ID: %d)\n", data.Name, data.ID)
See? No type name like User or Config. It’s just struct { ... }. You can pass this data variable around within the same function or scope, and it works just fine. But try to use it as a parameter or return type elsewhere, and you’ll quickly see its limitation.
The Giant Footgun: Type Identity
Here’s the first thing you need to burn into your brain: two anonymous structs are only the same type if their field declarations are exactly identical, down to the order and tags.
This isn’t a theoretical concern; it will bite you, especially with function parameters.
func processUser(u struct{ Name string }) {
fmt.Println(u.Name)
}
func main() {
// This works: the type signatures match exactly.
user1 := struct{ Name string }{Name: "Bob"}
processUser(user1) // "Bob"
// This DOES NOT COMPILE. The fields are in a different order.
user2 := struct{
Age int
Name string
}{Name: "Carol", Age: 30}
// processUser(user2) // Compiler Error: cannot use user2 as type struct{ Name string }
// This also DOES NOT COMPILE. The field *names* are different.
user3 := struct{ Username string }{Username: "Dave"}
// processUser(user3) // Compiler Error: cannot use user3...
}
The compiler sees struct{ Name string } and struct{ Age int; Name string } as two completely distinct, unrelated types. This makes them utterly useless for any kind of shared API or function signature that you plan to use with more than one literal. If you find yourself needing that, you should have defined a named type five minutes ago.
The Killer App: Ad-Hoc JSON Unmarshaling
This is where anonymous structs genuinely shine and justify their existence. You’re hitting an API that returns a massive JSON object with 50 fields, but you only care about two of them. Defining a full struct feels like overkill. Enter the anonymous struct.
// Imagine this is a 1000-line JSON response from some overly verbose API...
jsonBlob := `{
"totalResults": 100,
"startIndex": 1,
"itemsPerPage": 10,
"resources": [...a giant array...],
"queryTime": "52ms",
"sessionId": "abc123",
"user": {
"id": 456,
"preferences": {...},
"profile": {
"firstName": "Jane",
"lastName": "Doe",
"title": "Dr.",
"department": "Quantum Physics"
}
}
}`
// But all I want is the user's first and last name. Watch this.
var result struct {
User struct {
Profile struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
} `json:"profile"`
} `json:"user"`
}
err := json.Unmarshal([]byte(jsonBlob), &result)
if err != nil {
panic(err)
}
fmt.Printf("Welcome, %s %s\n", result.User.Profile.FirstName, result.User.Profile.LastName)
// Output: Welcome, Jane Doe
We unmarshaled that gigantic nested JSON payload directly into our precisely targeted anonymous struct. The JSON tags tell the unmarshaler exactly where to find our two precious fields, and it blissfully ignores the other 99% of the data. This is clean, efficient, and perfectly readable. It’s the correct tool for this job.
Best Practices and When to Avoid
- Keep Them Local: Their use should be confined to a single function or at most a single file. If you’re passing an anonymous struct type across package boundaries, you’ve made a terrible mistake. Use a named type.
- For One-Offs Only: They are perfect for single-use scenarios, like that JSON example or a quick data grouping for a template.
- Clarity Over Cleverness: Don’t use an anonymous struct just because you can. If the structure has semantic meaning (like a
User,Product,Config), give it a name. Your colleagues (and future you) will thank you for the clarity. - Beware of Repetition: If you catch yourself writing the same anonymous struct literal in multiple places, that’s the universe screaming at you to stop and define a named type.
So, in summary: anonymous structs are your handy, disposable scalpel for very specific, local surgeries. They are not the sledgehammer you use to build your entire application’s data model. Use them wisely, and they’ll save you time. Abuse them, and you’ll create a maintenance nightmare of incompatible, ad-hoc types. You’ve been warned.