The Anonymous Struct

Sometimes you need a thing with named parts. A map can only contain values if they are the same type or if you are willing to cast back and forth to the interface{} type. A struct is the obvious choice. But sometimes the need for the struct is only within a single function. Defining standard “named” structs for these one-off situations would litter your code with type definitions. An example would be passing data to a template or encoder. The struct is used once. It is used nowhere else in the code and populated just before being passed to the template.

The OG Struct

Basic structs have fields with names and types. For example, let’s say we have a struct that represents a person. The struct has fields for first name and last name. The structs are of type string. In code, we can refer to the parts of the struct by name.

package main

import "fmt"

type Person struct {
	LastName, FirstName string
}

func main() {
	p := Person {
		FirstName: "Jane",
		LastName: "User",
	}

	fmt.Printf("Hello %s, %s\n", p.LastName, p.FirstName)
}

We can refer to the fields of the struct if we pass the struct to a template. Encoders, like the JSON encoder, can read the fields and extract data.

	someTemplate := template.New("example")
	someTemplate.Parse(`Hello {{.FirstName}} {{.LastName}}`)
	someTemplate.Execute(os.Stdout, p)

    encoder := json.NewEncoder(os.Stdout)
	encoder.Encode(p)

However, the text/template package knows nothing about our struct. Nor does the JSON encoder package. They obtain their information through reflection. The data is passed as an interface{} even though we have a named type. We aren’t referring to the field FirstName. Rather we are passing field name whose value is extracted through reflection.

The Struct With No Name

The following code illustrates the use of an anonymous struct. Sometimes it’s not practical to create a struct for every type you need to pass to a template or serialize to JSON. The type is used once and it is simple, comprising of a few fields.

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"text/template"
)

func main() {
	p := struct{
		LastName, FirstName string
		age int
	} {
		FirstName: "Jane",
		LastName: "User",
		age: 43,
	}

	fmt.Printf("Hello %s, %s age %d\n", p.LastName, p.FirstName, p.age)

	someTemplate := template.New("example")
	someTemplate.Parse(`Hello {{.FirstName}} {{.LastName}} {{.age}}`)
	someTemplate.Execute(os.Stdout, p)

	encoder := json.NewEncoder(os.Stdout)
	encoder.Encode(p)
}

This anonymous struct is defined as having two fields, a FirstName and a LastName field. Both are type string. It also has a private field, age, of type int. It’s also possible to add tags, such as the json serialization tags. I then initialize the contents of the struct. I’m able to refer to the elements of the struct using its field names.

Hello User, Jane age 43
Hello Jane User {"LastName":"User","FirstName":"Jane"}

Within the context of main, I’m able to refer to the private member age. However, the template in another package and won’t have access to private members. I get the odd output above when I reference the age field in the template.

Passing

Anonymous struct types can be used as method parameters and return types. The only scenario this might be useful is if there are several (more than 3 or 4) return values. The anonymous struct might simplify handling the returned values.

Anonymous struct are less useful as parameters. The definition of the anonymous struct as a function parameter is cumbersome. There doesn’t appear to be a way to define a member function with an anonymous struct. For example, I can’t write func (p struct{FirstName string; LastName string}) String() string { return "" }.

package main

import (
	"fmt"
)

func MakeStruct(firstName, lastName string) struct{FirstName string; LastName string} {
	return struct{FirstName string; LastName string} {
		FirstName: firstName,
		LastName: lastName,
	}
}

func PrintStruct(x struct{FirstName string; LastName string}) string {
	return fmt.Sprintf("%s, %s", x.LastName, x.FirstName)
}

func main() {
	p := MakeStruct("Joe", "User")
	fmt.Println(PrintStruct(p))
}

Wait… Equality Works?

One thing I did not expect is to test for equality between an anonymous and a named struct. In the code below we see that the anonymous struct are clearly not the same type. The type for p1, p2, and p3 is main.Person. The type for p4 is struct { FirstName string; LastName string }. The Stringer implementation only works on main.Persons.

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	FirstName, LastName string
}

func (p Person) String() string {
	return fmt.Sprintf("%s, %s", p.LastName, p.FirstName)
}

func main() {
	p1 := Person{FirstName: "Joe", LastName: "User"}
	p2 := Person{FirstName: "Joe", LastName: "User"}
	p3 := Person{FirstName: "Nedward", LastName: "Steamragen"}

	fmt.Printf("%v = %v? %v\n", p1, p2, p1 == p2)
	fmt.Printf("%v = %v? %v\n", p1, p3, p1 == p3)

	p4 := struct {FirstName string; LastName  string} {
		FirstName: "Joe",
		LastName:  "User",
	}

	fmt.Printf("%v = %v? %v\n", p4, p1, p4 == p1)
	fmt.Printf("Type of %v = %v\n", p1, reflect.TypeOf(p1))
	fmt.Printf("Type of %v = %v\n", p4, reflect.TypeOf(p4))
	fmt.Printf("Are the types equal? %v\n", reflect.TypeOf(p1) == reflect.TypeOf(p4))
	fmt.Printf("%v = %v? %v\n", p4, p3, p4 == p3)
}

Testing for equality between a named and an anonymous struct appears to be valid. I double checked to make sure their types were really not equal, just in case I was doing ‘something wrong’. I was under the impression that equality required the types to be the same.

User, Joe = User, Joe? true
User, Joe = Steamragen, Nedward? false
{Joe User} = User, Joe? true
Type of User, Joe = main.Person
Type of {Joe User} = struct { FirstName string; LastName string }
Are the types equal? false
{Joe User} = Steamragen, Nedward? false

The following code, for example, does not even compile. While WorkingPerson is a type based directly on Person, they are not the same type. Similarly, if I define a type Celsius of float64 and a type Fahrenheit, also of float64, I don’t expect them to be comparable. Being able to compare an anonymous struct with a named struct is a surprising result. I would not rely on this behavior in my code.

type Person struct {
	FirstName, LastName string
}

func (p Person) String() string {
	return fmt.Sprintf("%s, %s", p.LastName, p.FirstName)
}

type WorkingPerson Person

func main() {
	p1 := Person{FirstName: "Joe", LastName: "User"}
	p2 := WorkingPerson{FirstName: "Joe", LastName: "User"}

	fmt.Printf("%v = %v? %v\n", p1, p2, p1 == p2)
}

Summary

Let’s place anonymous structs in their context. They are sometimes as useful as a map. Because the fields are declared with a type, they can be more convenient than casting to and from interface{}. They can be passed to and from functions, even if the syntax is clunky. There is some notion of equality with named structs, but this should be avoided.

It makes infinitely more sense to define a named struct for most other situations. For one thing, I don’t see how you can implement interfaces for anonymous structs. If you’re passing anonymous structs to anything but templates and encoders, the syntax is cumbersome. A map may be more convenient if the fields are all the same type, or there are more than a handful of fields. If you need to test for equality between to instances of a struct, a named struct is a better choice.