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.
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.
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.
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.
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.
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 :)
Get my free newsletter every second week
Used by 500+ developers to boost their Go skills.
"I'll share tips, interesting links and new content. You'll also get a brief guide to time for developers for free."