Anonymous structs in Go: What, How and When

Avatar of the author Willem Schots
16 Nov, 2023
~8 min.
RSS

Have you run into an elaborate struct definition with a struct inside a struct? Or, has someone told you to “use an inline struct definition”?

If you’re not sure how to approach any of this, then you’re in the right place.

This article will discuss anonymous structs: what they are and when to consider using them.

What is an anonymous struct?

You probably know that you can define struct types using a type definition:

type Album struct {
	ID     int
	Title  string
	Artist string
}

This is the typical way to define struct types. It forces you to name the struct type, in the above example the name of our struct type is Album.

This name can then be used in every place where Go accepts a type. A variable declaration for example:

var named Album

However, this is not the only way to define struct types. You can also define them anonymously, without giving them a name.

Go will still need to know the exact “shape” of your struct, you will need to provide the entire struct definition in place of a name to use an anonymous struct.

This is sometimes referred to as "defining a struct inline".

If we define our a new variable with an anonymous struct type, it looks like this:

var anon struct{
	ID     int
	Title  string
	Artist string
}

This code places the entire struct{...} definition where the Album name was in the earlier declaration.

In both examples, the variables are assigned zero values by default. We’re not assigning any initial values.

The zero value for a struct will be the struct with all fields set to their zero values.

If we do assign an initial value you will see that anonymous structs can lead to some repetitive code.

First, let’s look how it looks when we assign with a named struct.

var named Album = Album{
	ID:     101,
	Title:  "Hot Rats",
	Artist: "Frank Zappa",
}

// ... or using a short assignment:
named := Album{
	ID:     101,
	Title:  "Hot Rats",
	Artist: "Frank Zappa",
}

This is probably somewhat familiar. Now let’s look at the same assignment using an anonymous struct.

var anon struct{
	ID     int
	Title  string
	Artist string
} = struct{
	ID     int
	Title  string
	Artist string
}{
	ID:     101,
	Title:  "Hot Rats",
	Artist: "Frank Zappa",
}

That’s quite repetitive. If we miss the second struct{...} type identifier, we will get syntax errors like:

syntax error: unexpected {, expected expression
syntax error: non-declaration statement outside function body

Luckily we can skip the first struct{...} type identifier by using the := short assignment statement.

anon := struct{
	ID int
	Title string
	Artist string
}{
	ID:     101,
	Title:  "Hot Rats",
	Artist: "Frank Zappa",
}

Below are some more examples that show how anonymous contexts look in different contexts.

Example: Slice element

This example shows an anonymous struct as a slice element. Just like with other types, it’s not necessary to repeat the type for each slice element.

main.go
package main

import (
	"fmt"
)

func main() {
	s := []struct{
		ID     int
		Title  string
		Artist string
	}{
		{
			ID:     101,
			Title:  "Hot Rats",
			Artist: "Frank Zappa",
		},
		{
			ID:     123,
			Title:  "Troutmask Replica",
			Artist: "Captain Beefheart and his Magic Band",
		},
	}
	
	fmt.Println(s)
}

Example: Map element

This example shows an anonymous struct as a map element. Again, it’s not required to repeat the type for each element.

main.go
package main

import (
	"fmt"
)

func main() {
	m := map[string]struct{
		ID     int
		Title  string
		Artist string
	}{
		"zappa": {
			ID:     101,
			Title:  "Hot Rats",
			Artist: "Frank Zappa",
		},
		"beefheart": {
			ID:     123,
			Title:  "Troutmask Replica",
			Artist: "Captain Beefheart and his Magic Band",
		},
	}
	
	fmt.Println(m)
}

Example: Struct field

In this example you can see a named struct with an anonymous inner struct. Just like with our initial variable assignments we now need to specify the inner type definition again.

main.go
package main

import (
	"fmt"
)

type Discography struct {
	Name     string
	Featured struct{
		ID     int
		Title  string
		Artist string
	}
}

func main() {
	d := Discography{
		Name: "Frank Zappa",
		Featured: struct{
			ID     int
			Title  string
			Artist string
		}{
			ID:     101,
			Title:  "Hot Rats",
			Artist: "Frank Zappa",
		},
	}
	
	fmt.Println(d)
}

When to use anonymous structs?

As far as I tell there is no technical reason to use anonymous structs, they won’t make your programs more efficient.

All their value is due to what they communicate to other developers or readers of your source code.

Anonymous structs are a way to emphasize that a data type is only relevant in a very specific situation. A situation so specific, that a single type definition is all you need.

If you end up having to repeat the anonymous struct definition in multiple places, it’s likely a lot easier to use a named definition.

Common uses

If this all seems somewhat vague, well… it is. How/when to use anonymous structs is mostly a matter of taste and can differ between code bases and developers.

In my experience, there are two relatively common patterns that use anonymous structs

1. Table tests

Table tests are a way to organize your test data inside a “table”.

The “table” consists of columns that contain input and expected output for the function that is being tested. These columns are usually highly specific to this function, it’s not uncommon for every “table” to be structured differently.

For example, suppose we’re testing a function called Add that adds two integers x and y, we might have test cases like this:

x y wanted result
0 0 0
1 0 1
0 1 1
1 1 2
-1 0 -1
0 -1 -1
-1 -1 -2

A very common way to implement these “tables” is using a map or slice with an anonymous struct as its element type. The example below uses a slice.

main.go
package main

import (
	"fmt"
	"testing"
)

func Add(x, y int) int {
	return x + y
}

func TestAdd(t *testing.T) {
	tests := []struct {
		x    int
		y    int
		want int
	}{
		{x: 0, y: 0, want: 0},
		{x: 1, y: 0, want: 1},
		{x: 0, y: 1, want: 1},
		{x: 1, y: 1, want: 2},
		{x: -1, y: 0, want: -1},
		{x: 0, y: -1, want: -1},
		{x: -1, y: -1, want: -2},
	}

	for _, tc := range tests {
		name := fmt.Sprintf("%d+%d=%d", tc.x, tc.y, tc.want)
		t.Run(name, func(t *testing.T) {
			got := Add(tc.x, tc.y)
			if got != tc.want {
				t.Errorf("want %d got %d", tc.want, got)
			}
		})
	}
}

Anonymous structs are suitable because these “tables” are highly specific to the function that is being tested. If you would use named types they would likely only see a single use.

2. One-off unmarshalling targets

Sometimes you will need to map formatted data to a function. It can be useful to have an intermediate struct to unmarshal the formatted data into.

If this unmarshalling is only done in a single place in your source code, an anonymous struct be a suitable data type for this intermediate struct.

For example, let’s say that we’re creating a HTTP handler that maps JSON to a function call. In this case, we’re mapping a message and number to the doSomething function.

main.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

func main() {
	in := "{\"Message\": \"Hello world\", \"Number\": 101}"
	req := httptest.NewRequest("POST", "/", strings.NewReader(in))
	rr := httptest.NewRecorder()

	// handle will map the JSON data to a function call.
	handle(rr, req)
}

func handle(w http.ResponseWriter, r *http.Request) {
	// args is an anonymous struct.
	var args struct {
		Message string
		Number  int
	}

	// decode to the anonymous struct or error.
	err := json.NewDecoder(r.Body).Decode(&args)
	if err != nil {
		http.Error(w, "invalid json", http.StatusBadRequest)
		return
	}

	// map the anonymous struct to the function call.
	doSomething(args.Message, args.Number)
}

func doSomething(msg string, number int) {
	fmt.Println(msg, number)
}

In this example the args struct is used as an “inbetween” unmarshalling target. Since there is no need to reuse it, it’s not exposed outside of the handler.

There is a balance to be reached here, if the number of fields gets unwieldy you might want to have the function accept a named struct type instead.

Outro

I hope this article gives you some insight into anonymous structs. We discussed:

  • The syntax for anonymous structs.
  • Their value is in communication, not anything technical.
  • Two common use cases: Table tests and unmarshalling targets.

If you have any questions or comments, feel free to reach out to me :)

🎓

Subscribe to my Newsletter and Keep Learning.

Gain access to more content and get notified of the latest articles:

I send emails every 1-2 weeks and will keep your data safe. You can unsubscribe at any time.

Hello! I'm the Willem behind willem.dev

I created this website to help new Go developers, I hope it brings you some value! :)

You can follow me on Twitter/X, LinkedIn or Mastodon.

Thanks for reading!