Leerer Slice in Go: Wie man von Anfang an keine Fehler macht
\n\nJeder Go-Entwickler steht früher oder später vor der Frage: "Wie initialisiert man einen leeren Slice korrekt?". Scheinbar eine einfache Aufgabe, aber so eindeutig ist es nicht. Im offiziellen Go-FAQ und auf Stack Overflow sind diesem Thema hunderte Kommentare und tausende Stimmen gewidmet. Warum?
\n\nWeil es in Go zwei grundlegend verschiedene Möglichkeiten gibt, einen leeren Slice zu erstellen, und die Wahl zwischen ihnen beeinflusst die Performance, die Lesbarkeit des Codes und sogar das Verhalten bei der Serialisierung. Wenn Sie in Go programmieren und sauberen, effizienten Code ohne Überraschungen schreiben möchten, ist dieser Artikel für Sie.
\n\nWir werden analysieren:
\n- \n
- Was ist der Unterschied zwischen
var s []intunds := make([]int, 0)\n - Wann man einen nil-Slice und wann einen leeren Slice verwendet \n
- Wie sich dies auf JSON, Speicher und Performance auswirkt \n
- Was die Go-Gurus und die Autoren der Standardbibliothek sagen \n
Zwei Zwillingsbrüder: nil slice vs empty slice
\n\nIn Go ist ein Slice eine Struktur aus drei Feldern: Zeiger auf ein Array, Länge und Kapazität. Wenn wir von einem leeren Slice sprechen, meinen wir einen Slice mit der Länge 0. Aber wie wir ihn erstellen, ist entscheidend.
\n\nMethode 1: var-Deklaration (nil slice)
\n\nvar s []int // s == nil, len(s) == 0, cap(s) == 0\n\nDies ist ein nil-Slice. Er zeigt auf kein Array im Speicher. Dennoch können Sie bedenkenlos append darauf aufrufen – Go weist den Speicher automatisch zu.
Methode 2: make-Initialisierung (empty slice)
\n\ns := make([]int, 0) // s != nil, len(s) == 0, cap(s) == 0\n\nDies ist ein leerer Slice. Er zeigt bereits auf ein Null-Array (eine spezielle zerobase im Go-Runtime).
\n\nAuf den ersten Blick ist der Unterschied nicht erkennbar. Aber lassen Sie uns tiefer graben.
\n\nKonsolenausgabe und JSON – die erste Falle
\n\nVersuchen Sie, diesen Code auszuführen:
\n\npackage main\n\nimport (\n "encoding/json"\n "fmt"\n)\n\nfunc main() {\n var nilSlice []int\n emptySlice := make([]int, 0)\n\n fmt.Printf("nilSlice: %v, is nil: %v\\n", nilSlice, nilSlice == nil)\n fmt.Printf("emptySlice: %v, is nil: %v\\n", emptySlice, emptySlice == nil)\n\n // JSON-Serialisierung\n nilJSON, _ := json.Marshal(nilSlice)\n emptyJSON, _ := json.Marshal(emptySlice)\n\n fmt.Printf("nilSlice JSON: %s\\n", nilJSON)\n fmt.Printf("emptySlice JSON: %s\\n", emptyJSON)\n}\n\nDas Ergebnis wird Sie überraschen:
\n\nnilSlice: [], is nil: true\nemptySlice: [], is nil: false\nnilSlice JSON: null\nemptySlice JSON: []\n\nHier ist die größte Überraschung! Die JSON-Serialisierung verhält sich unterschiedlich. Wenn Ihre API [] in der Antwort erwartet, Sie aber einen nil-Slice übergeben, erhält der Client null. Und das bricht Verträge.
Viele REST-Clients (insbesondere in JavaScript) prüfen streng: Wenn in der Antwort ein Array erwartet wird, aber null ankommt, kann ein Fehler auftreten oder die Logik im Frontend gestört werden.
Performance: Gibt es einen Unterschied?
\n\nDie kurze Antwort: Ja, aber in 99% der Fälle ist er unbedeutend.
\n\nWenn Sie make([]int, 0) verwenden, weist Go sofort Speicher für die Slice-Struktur zu (24 Bytes auf einem 64-Bit-System) und verbindet sie mit der zerobase. Bei Verwendung von var erfolgt die Zuweisung erst beim ersten append.
Wenn Sie einen Slice erstellen, der höchstwahrscheinlich leer bleibt, spart der nil-Slice eine Speicherzuweisung. Wenn Sie jedoch genau wissen, dass Sie Elemente hinzufügen werden, bietet make mit Angabe der Kapazität eine bessere Performance:
// Wenn Sie die ungefähre Größe kennen, geben Sie die capacity an\ns := make([]int, 0, 100) // Kapazität 100, Länge 0\nfor i := 0; i < 100; i++ {\n s = append(s, i) // ohne Neuzuweisungen!\n}\n\nAber für die meisten Anwendungsfälle ist der Unterschied in der