What Are Golang's Anonymous Structs?
An anonymous struct is just like a normal struct, but it is defined without a name and therefore cannot be referenced elsewhere in the code.
Structs in Go are similar to structs in other languages like C. They have typed collections of fields and are used to group data to make it more manageable for us as programmers.
To create an anonymous struct, just instantiate the instance immediately using a second pair of brackets after declaring the type:
newCar := struct {
make string
model string
mileage int
}{
make: "Ford",
model: "Taurus",
mileage: 200000,
}
Contrast this with creating a named struct type:
// declare the 'car' struct type
type car struct {
make string
model string
mileage int
}
// create an instance of a car
newCar := car{
make: "Ford",
model: "taurus",
mileage: 200000,
}
If you’re interested in doing a deep dive into the Go programming language, check out my “Learn Go” course on Boot.dev.
When should I use an anonymous struct?
I often use anonymous structs to marshal and unmarshal JSON data in HTTP handlers. If a struct is only meant to be used once, then it makes sense to declare it in such a way that developers down the road won’t be tempted to accidentally use it again.
Take a look at the code below. We can marshal the HTTP request directly into an unnamed struct inline. All the fields are still accessible via the dot operator, but we don’t have to worry about another part of our project trying to use a type that wasn’t intended for it.
func createCarHandler(w http.ResponseWriter, req *http.Request) {
defer req.Body.Close()
decoder := json.NewDecoder(req.Body)
newCar := struct {
Make string `json:"make"`
Model string `json:"model"`
Mileage int `json:"mileage"`
}{}
err := decoder.Decode(&newCar)
if err != nil {
log.Println(err)
return
}
makeCar(newCar.Make, newCar.Model, newCar.Mileage)
return
}
Don’t use map[string]interface{} for JSON data if you can avoid it.
Instead of declaring a quick anonymous struct for JSON unmarshalling, I’ve often seen map[string]interface{} used. This is terrible in most scenarios for several reasons:
- No type checking. If the client sends a key called “name” with a
boolvalue, but your code is expecting astring, then unmarshalling into a map won’t catch the error - Maps are vague. After unmarshalling the data, we are forced to use runtime checks to make sure the data we care about exists. If those checks aren’t thorough, it can lead to a nil pointer dereference panic being thrown.
map[string]interface{}is verbose. Digging into the map isn’t as simple as accessing a named field using a dot operator, for example,newCar.model. Instead, it is something like:
func createCarHandler(w http.ResponseWriter, req *http.Request) {
myMap := map[string]interface{}{}
decoder := json.NewDecoder(req.Body)
err := decoder.Decode(&myMap)
if err != nil {
log.Println(err)
return
}
model, ok := myMap["model"]
if !ok {
fmt.Println("field doesn't exist")
return
}
modelString, ok := model.(string)
if !ok {
fmt.Println("model is not a string")
}
// do something with model field
}
Anonymous structs can clean up your API handlers if used properly. The strong typing they offer while still being a “one-off” solution is a powerful tool.
Bonus - Use a slice of anonymous structs for easy test data
Anonymous structs are great for writing table driven tests.
var cars = []struct {
make string
model string
topSpeed
}{
{"toyota", "camry", 100},
{"tesla", "model 3", 250},
{"ford", "focus", 120},
}
Bonus #2 - Group a set of global (gasp) variables in a struct
var apiSettings struct {
secret string
dbConn string
}
apiSettings.secret = "super-secr3t-p@$$"
Related Articles
Announcing Go-TinyTime, Go-TinyDate's Sister Package
Apr 02, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
time.Time is the perfect choice for handling times in Go in most cases, it even comes in the standard library! The problem is that the time.Time{} struct uses more than 24 bytes of memory under most conditions. Go-TinyTime solves this problem by restricting the available dates to the range between 1970 - 2106, and only supporting UTC timezones. This brings data usage down to just 4 bytes of memory.
Golang Conversions - Ints To Strings And Strong Typing
Mar 31, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Go is a strongly typed language, which means at any point a developer should know exactly what type of value they are dealing with. For example, if we have a function that prints a string, we can’t just give it an integer and expect it to work. We have to cast it to a string explicitly:
How To Separate Library Packages in Go
Mar 29, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level.
I Wrote Go-TinyDate, The Missing Golang Date Package
Mar 23, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
time.Time makes dealing with dates and times in Go a breeze, and it even comes bundled in the standard library! However, a time.Time{} struct uses more than 24 bytes of memory under most conditions, and I’ve run into situations where I need to store millions of them in memory, but all I really needed was a UTC date! Go-TinyDate solves this with just 4 bytes of memory.