Ein anonymer Struct ist ein Struct-Typ, der nirgendwo per type einen Namen bekommt — er wird direkt dort hingeschrieben, wo er gebraucht wird: in der Variablen-Deklaration, im Slice-Element-Typ, im Funktions-Parameter. Das klingt nach Spielerei, ist aber an drei Stellen das idiomatische Mittel der Wahl: Table-driven Tests, kleine JSON-Payloads und gruppierte Konfig-Felder. Dieser Artikel arbeitet die Mechanik aus der Spec heraus, zeigt die zwei produktiven Hauptmuster mit ausführlichem Code und macht die Grenzen explizit — keine Methoden, kein sinnvolles Interface-Spiel, keine Wiederverwendung über Package-Grenzen.
Was ist ein anonymer Struct?
Ein „normaler" Struct in Go entsteht in zwei Schritten: erst die Typ-Deklaration mit type, dann die Variable mit dem deklarierten Namen.
type Point struct {
X, Y int
}
var p Point = Point{X: 1, Y: 2}Der anonyme Struct überspringt den ersten Schritt. Der Typ steht direkt an der Variable — keine separate Deklaration, kein Name, kein zweiter Verweis im Package möglich.
package main
import "fmt"
func main() {
// Typ und Wert in einem Ausdruck — kein „type Point" nötig.
p := struct {
X, Y int
}{X: 1, Y: 2}
fmt.Printf("%+v\n", p)
}{X:1 Y:2}Die Spec deckt das im Abschnitt Struct types explizit ab — ein Struct-Typ-Literal ist überall dort gültig, wo ein Typ erwartet wird:
A struct type T may not contain a field of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or struct types.
Das einzige Strukturmerkmal: kein Name. Alles andere — Felder, Tags, Embedding, Zero Value, Composite-Literal-Syntax — funktioniert identisch zum benannten Pendant.
Composite Literal direkt — der häufigste Anwendungsweg
Wer einen anonymen Struct nutzt, hat fast immer das gleiche Muster im Sinn: ein Composite Literal mit eingebettetem Typ. Die Spec-Grammatik fordert LiteralType LiteralValue — beides darf inline stehen.
package main
import "fmt"
func main() {
// Positional — Felder in Deklarations-Reihenfolge.
a := struct{ X, Y int }{1, 2}
// Mit Feldnamen — robuster gegen Umordnung.
b := struct{ X, Y int }{X: 3, Y: 4}
// Felder weglassen — fehlende werden Zero Value.
c := struct{ X, Y int }{X: 5}
fmt.Println(a, b, c)
}{1 2} {3 4} {5 0}Die drei Varianten sind die gleichen wie bei benannten Structs. Faustregel: in Test-Tabellen und Konfig-Objekten immer mit Feldnamen schreiben — sonst zerstört eine spätere Feld-Umordnung jeden Aufrufer.
Wann sinnvoll — lokal, einmalig, kontextgebunden
Anonyme Structs sind kein Allzweck-Werkzeug. Sie passen in genau die Situationen, in denen ein eigener type-Name Lärm wäre:
- Lokal: der Struct lebt innerhalb einer Funktion, einer Test-Tabelle, eines Initializers.
- Einmalig: er wird an genau einer Stelle gebaut und konsumiert.
- Kontextgebunden: der Name würde keine Information tragen, weil der Kontext (Funktion, Test, Payload-URL) ihn bereits liefert.
Sobald auch nur einer dieser drei Punkte wackelt — du baust den gleichen Struct an zwei Stellen, ein Aufrufer braucht den Typ als Parameter, der Struct lebt länger als die umgebende Funktion — kippt die Wahl zurück zum benannten Typ.
// GUT — anonymer Struct als lokales Aggregat in einer Funktion.
func summary() {
stats := struct {
Files int
Lines int
}{Files: 12, Lines: 3402}
_ = stats
}
// SCHLECHT — derselbe Struct an zwei Stellen.
// Wenn dieser Shape auch in summary2() vorkommt, gehört er in
// einen benannten Typ — sonst musst du beide Stellen synchron halten.Hauptanwendung — Table-driven Tests mit Subtests
Das mit Abstand häufigste Einsatzgebiet anonymer Structs in echten Go-Projekten ist die Test-Tabelle. Du beschreibst Eingaben und erwartete Ausgaben als Slice von Structs, iterierst mit t.Run für je einen Subtest und kommst ohne separate Typ-Deklaration aus.
package parser
import "testing"
func TestParseDuration(t *testing.T) {
tests := []struct {
name string
input string
want int // Sekunden
wantErr bool
}{
{"sekunden", "30s", 30, false},
{"minuten", "2m", 120, false},
{"stunden", "1h", 3600, false},
{"kombiniert", "1h30m", 5400, false},
{"leer", "", 0, true},
{"unbekannte einheit", "5d", 0, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := ParseDuration(tc.input)
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
}
if got != tc.want {
t.Errorf("ParseDuration(%q) = %d, want %d", tc.input, got, tc.want)
}
})
}
}Drei Eigenschaften machen das Pattern unschlagbar:
- Der Test-Fall-Typ wird genau dort beschrieben, wo er gebraucht wird. Wer den Test liest, sieht in derselben Bildschirmseite, welche Felder existieren und welche Werte sie annehmen.
t.Run(tc.name, ...)macht aus jeder Tabellen-Zeile einen eigenständigen Subtest mit eigenem Namen — sichtbar in-v-Output, einzeln filterbar viago test -run TestParseDuration/leer.- Kein Namens-Sprawl im Package. Du brauchst keine
testCase-Struct-Definition, die in Wahrheit nur in dieser einen Test-Funktion existiert.
Das Pattern ist so etabliert, dass es in der offiziellen Go-Testing-Wiki als Empfehlung steht und sich quer durch die Stdlib zieht — strings, bytes, encoding/json, net/http benutzen es überall.
JSON-Payload inline
Die zweite große Domäne sind kurzlebige JSON-Payloads — Request-Bodies an externe APIs, Response-Decodings für einen einzigen Endpunkt, Konfig-Snippets aus einer Datei.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("https://api.github.com/repos/golang/go")
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Wir interessieren uns nur für zwei Felder der riesigen Response.
// Ein benannter Typ wäre Overkill — der anonyme Struct macht
// explizit, dass dieses Shape nur hier lebt.
var payload struct {
Name string `json:"name"`
Stars int `json:"stargazers_count"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
panic(err)
}
fmt.Printf("%s: %d Sterne\n", payload.Name, payload.Stars)
}go: 128340 SterneBemerkenswert: Struct-Tags funktionieren genauso wie bei benannten Typen. Das ist eine direkte Konsequenz der Spec — Tags sind Teil der Feld-Deklaration, nicht des Typ-Namens. encoding/json, encoding/xml und Validatoren wie go-playground/validator arbeiten mit anonymen Structs identisch zu benannten.
Genauso bei Request-Bodies:
func createIssue(title, body string) (*http.Response, error) {
// Inline-Payload — der Shape wird nur hier konstruiert.
payload := struct {
Title string `json:"title"`
Body string `json:"body"`
Labels []string `json:"labels,omitempty"`
}{
Title: title,
Body: body,
Labels: []string{"bug", "needs-triage"},
}
buf, err := json.Marshal(payload)
if err != nil {
return nil, err
}
return http.Post("https://api.example.com/issues",
"application/json", bytes.NewReader(buf))
}Faustregel: wenn die JSON-Form nur an genau einer Stelle gebraucht wird und nicht über Package-Grenzen wandert, ist der anonyme Struct die richtige Wahl. Sobald der gleiche Shape auch im Test-Code, in einem anderen Endpunkt oder als Rückgabe-Typ vorkommt — benannter Typ.
Konfig-Gruppe als anonymes Feld im Outer-Struct
Eine elegante, oft übersehene Anwendung: ein anonymer Struct als Feld in einem größeren Struct. Damit gruppierst du verwandte Felder logisch, ohne einen separaten Typ-Namen einzuführen.
package main
import "fmt"
type ServerConfig struct {
Addr string
// Logging-bezogene Felder zusammengefasst.
Log struct {
Level string
Format string
File string
}
// TLS-bezogene Felder zusammengefasst.
TLS struct {
Enabled bool
CertFile string
KeyFile string
}
}
func main() {
cfg := ServerConfig{Addr: ":8080"}
cfg.Log.Level = "info"
cfg.Log.Format = "json"
cfg.TLS.Enabled = true
cfg.TLS.CertFile = "/etc/ssl/server.crt"
fmt.Printf("%+v\n", cfg)
}{Addr::8080 Log:{Level:info Format:json File:} TLS:{Enabled:true CertFile:/etc/ssl/server.crt KeyFile:}}Der Zugriff cfg.Log.Level liest sich wie eine Konfig-Hierarchie aus einer YAML- oder TOML-Datei — und du brauchst kein Top-Level-type LogConfig struct {...}, das ohnehin nirgendwo wiederverwendet würde.
Grenzen des Patterns: du kannst die Inner-Gruppen nicht als eigenständigen Wert weiterreichen, ohne den vollen Typ-Ausdruck nochmal zu schreiben. Wenn Log in eine Hilfsfunktion soll, ist der benannte Typ besser.
Type-Identität — gleiche Felder, gleiche Tags, identische Typen
Die Spec ist im Abschnitt Type identity eindeutig:
Two struct types are identical if they have the same sequence of fields, and if corresponding pairs of fields have the same names, identical types, and identical tags, and are either both embedded or both not embedded.
Auf anonyme Structs angewendet heißt das: zwei anonyme Struct-Typen mit gleicher Feld-Sequenz, gleichen Namen, identischen Typen und identischen Tags sind identisch — Go behandelt sie als ein und denselben Typ, und du kannst zwischen ihnen frei zuweisen.
package main
import "fmt"
func main() {
a := struct{ X, Y int }{1, 2}
b := struct{ X, Y int }{3, 4}
// Identische Typen → direkte Zuweisung möglich.
a = b
fmt.Println(a)
// Auch über Funktions-Grenzen identisch:
takeIt(a)
}
func takeIt(p struct{ X, Y int }) {
fmt.Printf("got %+v\n", p)
}{3 4}
got {3 4}Die Identitäts-Regel kennt drei Stolperfallen:
- Feldreihenfolge zählt.
struct{ X, Y int }undstruct{ Y, X int }sind unterschiedliche Typen, obwohl die Felder gleich heißen. - Tags zählen. Zwei Structs, die sich nur in einem Tag unterscheiden, sind nicht identisch.
struct{ Name string }undstruct{ Name string \json:"name"` }` sind verschiedene Typen. - Unexportierte Felder aus unterschiedlichen Packages sind nie identisch — der Package-Pfad ist Teil der Identität.
In der Praxis: wer dieselbe anonyme Form an drei verschiedenen Stellen schreibt, hat dreimal denselben Typ — aber drei Stellen, die bei einer Schema-Änderung gleichzeitig angefasst werden müssen. Spätestens dann ist der benannte Typ besser.
Limit — keine Methoden
Die schärfste Einschränkung: anonyme Structs können keine Methoden haben. Die Spec verlangt für func (r Receiver) Name() einen Typ-Namen als Receiver — und ein anonymer Struct hat keinen.
// Geht NICHT — die Syntax dafür existiert in Go gar nicht.
// func (p struct{ X, Y int }) Sum() int { return p.X + p.Y }
//
// Compiler-Fehler:
// the receiver type must be a defined type or a pointer to a defined typeWer Verhalten braucht, muss den Typ benennen:
package main
import "fmt"
type Point struct {
X, Y int
}
func (p Point) Sum() int { return p.X + p.Y }
func main() {
p := Point{X: 3, Y: 4}
fmt.Println(p.Sum())
}7Konsequenz: sobald ein Typ Verhalten tragen soll, ist der anonyme Struct raus. Das ist nicht zufällig — es ist die natürliche Schranke, die anonyme Structs auf reine Daten-Aggregate beschränkt.
Limit — Interfaces praktisch unbrauchbar
Da anonyme Structs keine Methoden tragen, können sie auch keine Interfaces implementieren — jedenfalls keine, die Methoden verlangen. Das leere Interface any erfüllt jeder Wert, aber das ist keine Information.
type Stringer interface {
String() string
}
// Geht nicht — kein String() auf anonymen Structs möglich.
// var s Stringer = struct{ Name string }{Name: "x"}Es gibt eine theoretische Konstruktion mit eingebetteten benannten Typen, die Methoden mitbringen — aber das ist eine Spitzfindigkeit, kein praktisches Muster:
package main
import (
"fmt"
"strings"
)
func main() {
// Anonymer Struct mit eingebettetem strings.Builder.
// Erbt dadurch dessen Methoden — funktioniert, ist aber
// selten lesbarer als ein direkter benannter Wrapper-Typ.
buf := struct {
strings.Builder
Prefix string
}{Prefix: "> "}
buf.WriteString(buf.Prefix)
buf.WriteString("hallo")
fmt.Println(buf.String())
}> halloSolche Konstrukte tauchen in echtem Code praktisch nie auf — wer Methoden braucht, schreibt einen type-Namen und gut.
Limit — bei Wiederverwendung lieber benennen
Die Verlockung ist groß, denselben anonymen Struct an mehreren Stellen zu wiederholen, weil das Schreiben so schnell geht. Mach es nicht.
// ANTI-PATTERN — derselbe Shape an drei Stellen.
func loadUser() (struct {
ID int64
Name string
}, error) {
// ...
}
func saveUser(u struct {
ID int64
Name string
}) error {
// ...
}
// Spätere Schema-Änderung muss drei Stellen anfassen.
// Außerdem: die Aufrufer müssen den vollen Typ-Ausdruck kennen.Sobald derselbe anonyme Struct an zwei Stellen erscheint, ist der benannte Typ klar besser:
type User struct {
ID int64
Name string
}
func loadUser() (User, error) { /* ... */ }
func saveUser(u User) error { /* ... */ }Faustregel: die Schmerzgrenze ist 1. Ein anonymer Struct, der ein zweites Mal gebraucht wird, ist ein benannter Struct.
Praxis — vollständiger Table-driven-Test
Damit das Muster nicht abstrakt bleibt, hier ein kompletter Test gegen eine kleine Logik-Funktion — produktionsnah, mit Subtests, Fehler-Pfaden und mehreren Assertion-Punkten pro Zeile.
package pricing
import "errors"
var ErrInvalidQuantity = errors.New("quantity must be positive")
// CalculateTotal rechnet Preis mal Menge plus Steuer.
// Bei Mengen über 100 gibt es 10 % Rabatt.
func CalculateTotal(unitPrice float64, qty int, taxRate float64) (float64, error) {
if qty <= 0 {
return 0, ErrInvalidQuantity
}
subtotal := unitPrice * float64(qty)
if qty > 100 {
subtotal *= 0.9
}
return subtotal * (1 + taxRate), nil
}Der dazugehörige Test mit anonymem Struct als Tabellen-Element:
package pricing
import (
"errors"
"math"
"testing"
)
func TestCalculateTotal(t *testing.T) {
tests := []struct {
name string
unitPrice float64
qty int
taxRate float64
want float64
wantErr error
}{
{
name: "ohne rabatt, ohne steuer",
unitPrice: 10.0, qty: 5, taxRate: 0.0,
want: 50.0,
},
{
name: "ohne rabatt, mit 19 prozent steuer",
unitPrice: 10.0, qty: 5, taxRate: 0.19,
want: 59.5,
},
{
name: "mit mengenrabatt, ohne steuer",
unitPrice: 10.0, qty: 200, taxRate: 0.0,
want: 1800.0, // 10 * 200 * 0.9
},
{
name: "mit mengenrabatt und steuer",
unitPrice: 10.0, qty: 200, taxRate: 0.19,
want: 2142.0, // 1800 * 1.19
},
{
name: "menge null ist fehler",
unitPrice: 10.0, qty: 0, taxRate: 0.0,
wantErr: ErrInvalidQuantity,
},
{
name: "negative menge ist fehler",
unitPrice: 10.0, qty: -3, taxRate: 0.0,
wantErr: ErrInvalidQuantity,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CalculateTotal(tc.unitPrice, tc.qty, tc.taxRate)
if tc.wantErr != nil {
if !errors.Is(err, tc.wantErr) {
t.Fatalf("err = %v, want %v", err, tc.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if math.Abs(got-tc.want) > 1e-9 {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}Was diesen Test produktionstauglich macht:
- Eine Tabellen-Zeile pro Szenario — Lese-Reihenfolge entspricht Test-Ausführungs-Reihenfolge.
errors.Isfür Fehler-Vergleich — robust gegenfmt.Errorf("%w", ...)-Wrapping.- Float-Toleranz mit
math.Abs— Vergleich auf exakte Gleichheit wäre bei Float-Arithmetik fehleranfällig. - Anonymer Struct als Tabellen-Typ — kein
type pricingTestCase struct {...}nötig, das im Package nur diesen einen Test betreffen würde.
Praxis — Aggregations-Map mit anonymem Struct als Wert
Zweites Praxis-Beispiel aus dem Daten-Aggregations-Alltag: du sammelst pro Nutzer mehrere Statistiken in einer Map. Ein anonymer Struct als Map-Wert hält das lokal, ohne dass die Statistik-Struktur das halbe Package belegt.
package main
import "fmt"
type Event struct {
UserID string
Kind string // "login", "purchase", "click"
AmountEUR float64
}
// Aggregation: pro Nutzer Anzahl Logins, Käufe, Klicks und Gesamtumsatz.
func aggregate(events []Event) map[string]struct {
Logins int
Purchases int
Clicks int
TotalEUR float64
} {
stats := map[string]struct {
Logins int
Purchases int
Clicks int
TotalEUR float64
}{}
for _, e := range events {
// Map-Werte sind in Go nicht direkt adressierbar →
// erst raus, ändern, wieder rein.
s := stats[e.UserID]
switch e.Kind {
case "login":
s.Logins++
case "purchase":
s.Purchases++
s.TotalEUR += e.AmountEUR
case "click":
s.Clicks++
}
stats[e.UserID] = s
}
return stats
}
func main() {
events := []Event{
{UserID: "alice", Kind: "login"},
{UserID: "alice", Kind: "purchase", AmountEUR: 29.90},
{UserID: "alice", Kind: "click"},
{UserID: "bob", Kind: "login"},
{UserID: "bob", Kind: "purchase", AmountEUR: 12.50},
{UserID: "bob", Kind: "purchase", AmountEUR: 7.00},
}
for user, s := range aggregate(events) {
fmt.Printf("%s: %+v\n", user, s)
}
}alice: {Logins:1 Purchases:1 Clicks:1 TotalEUR:29.9}
bob: {Logins:1 Purchases:2 Clicks:0 TotalEUR:19.5}Hier zeigt sich die Grenze des Patterns: die anonyme Struct-Form taucht zweimal auf — einmal im Rückgabe-Typ von aggregate, einmal in der lokalen stats-Variable. Das ist genau das Wiederholungs-Signal aus Abschnitt 10. Sobald die Aggregations-Funktion in ein Package wandert und von draußen aufgerufen wird, muss der Typ einen Namen bekommen — sonst hat jeder Aufrufer denselben Typ-Ausdruck in seiner Signatur.
Die produktionsreife Variante:
type UserStats struct {
Logins int
Purchases int
Clicks int
TotalEUR float64
}
func aggregate(events []Event) map[string]UserStats {
// ...
}Der anonyme Struct hat hier seinen Dienst getan: er hat dir erlaubt, die Idee schnell zu prototypen. Sobald sie produktiv wird, kommt der Name.
Bonus — Empty Struct als Set-Marker
Ein Spezialfall verdient eigene Aufmerksamkeit: der leere anonyme Struct struct{}. Er hat keine Felder, belegt null Byte Speicher und wird in einem ganz konkreten Idiom verwendet — als Marker-Wert in Maps, um Sets zu simulieren.
package main
import (
"fmt"
"unsafe"
)
func main() {
// Ein Set ist eine Map von Key → struct{}.
seen := map[string]struct{}{}
for _, word := range []string{"foo", "bar", "foo", "baz", "bar"} {
seen[word] = struct{}{}
}
// Mitgliedschafts-Test mit comma-ok.
if _, ok := seen["foo"]; ok {
fmt.Println("foo gesehen")
}
if _, ok := seen["qux"]; !ok {
fmt.Println("qux nie gesehen")
}
fmt.Println("unique words:", len(seen))
fmt.Println("sizeof(struct{}):", unsafe.Sizeof(struct{}{}))
}foo gesehen
qux nie gesehen
unique words: 3
sizeof(struct{}): 0Warum nicht map[string]bool? Drei Argumente:
- Null Byte pro Wert —
boolbelegt mindestens ein Byte,struct{}exakt null. Bei großen Sets summiert sich das. - Eindeutige Semantik —
map[string]boollässt offen, obfalsebedeutet „nicht enthalten" oder „enthalten mit Wert false". Diestruct{}-Form drückt aus „die Map-Existenz selbst ist die Information". - Convention — der Set-mit-
struct{}-Wert ist ein anerkanntes Go-Idiom, jeder Reviewer erkennt es sofort.
Genauso für Signal-Channels:
// Signal-Channel — null Byte pro Sende-Vorgang.
// Die Information liegt im „dass etwas gesendet wurde", nicht im Wert.
done := make(chan struct{})
go func() {
// ... Arbeit ...
close(done)
}()
<-done // blockiert, bis close ausgelöst wirdDer chan struct{}-Idiom für Done-Signale ist in der Go-Concurrency-Welt ubiquitär — die Stdlib selbst nutzt ihn in context.Context.Done().
Besonderheiten
Anonyme Structs leben am besten lokal — eine Funktion, ein Test, ein Initializer.
Sobald der gleiche Shape an einer zweiten Stelle gebraucht wird, kippt die Wahl zum benannten Typ. Die Schmerzgrenze ist ehrlich gesagt 1: ein anonymer Struct in einem Funktions-Body ist gut, derselbe Struct als Funktions-Parameter und gleichzeitig als Variable ist bereits einer zu viel. Wiederholung erzeugt Synchronisations-Aufwand — und genau den vermeidest du normalerweise mit einem type-Namen.
In Table-driven Tests sind anonyme Structs der Default — nicht die Ausnahme.
Die Stdlib selbst macht es so: strings_test.go, bytes_test.go, net/http-Tests benutzen alle das Muster tests := []struct{...}{ {...}, {...} } mit anonymem Element-Typ. Wer einen Test-Case-Typ als benannten Top-Level-Typ in seinem Package hat, hat fast immer einen Code-Smell — der Typ lebt im Test, gehört in den Test, nichts außerhalb darf ihn brauchen.
`struct{}` belegt null Byte — `bool` belegt mindestens eins.
Der leere Struct ist die kanonische Wahl für Set-Marker und Signal-Channels. map[string]struct{} ist exakt null Byte pro Wert; map[string]bool mindestens ein Byte. Bei einer Million Einträgen ist das ein Megabyte Differenz — und es kommuniziert klarer: die Existenz des Keys ist die Information, kein zusätzlicher Wert nötig.
Struct-Tags funktionieren bei anonymen Structs identisch — JSON, XML, Validator-Libs sind blind für den Namen.
Tags sind Teil der Feld-Deklaration, nicht des Typ-Namens. Ein json:"name" auf einem Feld eines anonymen Structs wird von encoding/json exakt wie auf einem benannten Typ behandelt. Das macht den anonymen Struct zur idealen Wahl für API-Payloads, die nur an einer Stelle gebraucht werden — Request-Body bauen, Response decoden, fertig.
`go vet` und `gofmt` erkennen anonyme Structs vollständig — kein Tooling-Nachteil.
Manchmal taucht das Missverständnis auf, anonyme Structs würden mit Tools schlechter zusammenarbeiten. Stimmt nicht: alle Standard-Tools — go vet, staticcheck, gofmt, IDE-Refactorings — behandeln sie genauso wie benannte Typen. Der einzige praktische Unterschied ist, dass Reflection-Code mit reflect.TypeOf(x).Name() einen leeren String zurückgibt, weil der Typ nun mal keinen Namen hat.
Type-Identität greift auch über Funktions-Grenzen — gleicher Shape, identischer Typ.
Wenn f(p struct{ X, Y int }) deklariert ist und du q := struct{ X, Y int }{1, 2} baust, kannst du f(q) direkt aufrufen — die Spec sagt, beide Typen sind identisch. Das ist mächtig, hat aber die hässliche Kehrseite, dass die Funktions-Signatur den Typ-Ausdruck inline trägt. Bei mehr als zwei Feldern wird das schnell unleserlich; spätestens dann benannter Typ.
Methoden gehen nicht — wer Verhalten will, muss benennen.
Die Spec verlangt für Receiver einen defined type. Ein anonymer Struct ist kein defined type, also keine Methoden. Wer das versucht, bekommt vom Compiler the receiver type must be a defined type or a pointer to a defined type. Diese Schranke ist absichtlich — sie verhindert, dass anonyme Structs zu vollwertigen Mini-Klassen mutieren, und hält sie dort, wo sie hingehören: bei reinen Daten-Aggregaten.
Map-Werte sind nicht adressierbar — Felder anonymer Struct-Werte in Maps direkt zu ändern, geht nicht.
Bei m := map[string]struct{X int}{...} darfst du nicht m["k"].X = 1 schreiben — der Compiler verweigert, weil Map-Element-Ausdrücke nicht adressierbar sind. Du musst den Wert kopieren, ändern, zurückschreiben: v := m["k"]; v.X = 1; m["k"] = v. Das gilt für anonyme und benannte Struct-Werte gleichermaßen, fällt aber bei anonymen besonders auf, weil der Typ-Ausdruck so groß wird.
Weiterführende Ressourcen
Externe Quellen
- Struct types — Go Language Specification
- Composite literals — Go Language Specification
- Type identity — Go Language Specification
- Go Wiki: Table-driven tests
testingpackage — pkg.go.devencoding/jsonpackage — pkg.go.dev
Verwandte Artikel
- Structs — Composite Literals und Pointer-Receiver
- Struct-Tags — Metadaten an Feldern
- Value-Receiver vs. Pointer-Receiver — Konsistenz-Regel
- Map — Aggregation, Set-Marker, Iteration
- Funktionen mit mehreren Rückgabewerten — Tupel ohne anonymen Struct
- Interfaces — Übersicht und Method-Sets
- Pointer vs. Wert — Pass-by-Value, Mutation, Receiver-Typ