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.

Go benannt.go
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.

Go anonym.go
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)
}
Output
{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.

Go composite.go
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)
}
Output
{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.

Go lokal-einmalig.go
// 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.

Go parser_test.go
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 via go 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.

Go github-stars.go
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)
}
Output
go: 128340 Sterne

Bemerkenswert: 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:

Go request-body.go
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.

Go config.go
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)
}
Output
{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.

Go identitaet.go
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)
}
Output
{3 4}
got {3 4}

Die Identitäts-Regel kennt drei Stolperfallen:

  • Feldreihenfolge zählt. struct{ X, Y int } und struct{ 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 } und struct{ 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.

Go keine-methoden.go
// 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 type

Wer Verhalten braucht, muss den Typ benennen:

Go benannt-mit-methode.go
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())
}
Output
7

Konsequenz: 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.

Go kein-interface.go
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:

Go embedded-methods.go
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())
}
Output
> hallo

Solche 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.

Go anti-wiederholung.go
// 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:

Go benannt-wiederverwendung.go
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.

Go pricing.go
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:

Go pricing_test.go
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.Is für Fehler-Vergleich — robust gegen fmt.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.

Go user-stats.go
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)
    }
}
Output
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:

Go user-stats-named.go
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.

Go set.go
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{}{}))
}
Output
foo gesehen
qux nie gesehen
unique words: 3
sizeof(struct{}): 0

Warum nicht map[string]bool? Drei Argumente:

  • Null Byte pro Wertbool belegt mindestens ein Byte, struct{} exakt null. Bei großen Sets summiert sich das.
  • Eindeutige Semantikmap[string]bool lässt offen, ob false bedeutet „nicht enthalten" oder „enthalten mit Wert false". Die struct{}-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:

Go done-channel.go
// 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 wird

Der 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

/ Weiter

Zurück zu Structs & Methoden

Zur Übersicht