Make oder New? Die ewige Frage des Go-Entwicklers
Sie programmieren seit einer Woche oder einem Jahr in Go, aber zögern trotzdem manchmal eine Sekunde vor der Wahl: make() oder new()? Das ist normal. Die Frage "Why would I make() or new()?" auf Stack Overflow hat zehntausende Aufrufe gesammelt und verliert auch im Jahr 2025 nicht an Aktualität. Warum? Weil sich hinter der scheinbaren Einfachheit ein grundlegender Unterschied in der Philosophie der Speicherverwaltung in Go verbirgt.
Lassen Sie uns ein für alle Mal klären, was Sache ist, damit Sie nicht mehr raten müssen. Wir gehen von der Basis bis zu realen Fällen, beleuchten Fallstricke und geben eine Checkliste für die tägliche Arbeit. Los geht's!
Kurze Antwort (für Eilige)
Wenn Sie Speicher für Slice, Map oder Channel zuweisen müssen – verwenden Sie make(). In allen anderen Fällen – new() oder das Literal &Type{}. Aber der Teufel steckt, wie immer, im Detail.
Was ist new() und wie funktioniert es?
Die Funktion new(T) ist eine eingebaute Go-Funktion, die Speicher für den Typ T zuweist, ihn auf Null setzt (Zero Value) und einen Zeiger auf diesen Speicher zurückgibt: *T.
Einfach gesagt: new() gibt Ihnen ein leeres, aber gültiges Objekt. Sie erhalten einen Zeiger, aber das Objekt selbst ist noch nicht bereit für die vollständige Arbeit, wenn es eine Initialisierung interner Strukturen benötigt.
package main
import "fmt"
type Person struct { Name string Age int}
func main() { p := new(Person) fmt.Println(p) // Ausgabe: &{ 0} fmt.Println(p.Name == "") // true}Hier hat new(Person) Speicher für die Struktur zugewiesen, die Felder auf Null gesetzt (leerer String und 0) und einen Zeiger zurückgegeben. Alles korrekt.
Was ist make() und seine Superkraft
make() ist ebenfalls eine eingebaute Funktion, arbeitet aber nur mit drei Typen: Slices, Maps und Channels. Warum nur diese? Weil diese Typen eine interne Struktur (Header) haben, die initialisiert werden muss, und nicht einfach nur auf Null gesetzt werden kann.
make() gibt keinen Zeiger zurück. Es gibt einen initialisierten Wert des benötigten Typs zurück (keinen Zeiger!).
// Slice: Länge 5, Kapazität 10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10
// Map: bereit zum Einfügen von Elementenm := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]
// Channel: gepuffert mit 3 Nachrichtench := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1Beachten Sie: Wenn Sie versuchen, m := new(map[string]int) zu schreiben, erhalten Sie einen Zeiger auf eine nil-Map. Das Einfügen eines Elements in eine solche Map führt zu einer Panik (panic).
Warum funktioniert new() nicht für Slices, Maps und Channels?
Schauen wir uns das Beispiel eines Slices an. Ein Slice in Go ist eine Struktur aus drei Feldern: Zeiger auf das Array, Länge und Kapazität. new([]int) weist Speicher für diese Struktur zu und setzt sie auf Null. Der Zeiger wird nil sein, Länge und Kapazität sind 0. Sie erhalten &[] – einen Zeiger auf einen nil-Slice. Wird er bei der Zuweisung per Index kaputtgehen? Nein, er wird abstürzen.
make([]int, 3) hingegen macht drei Dinge:
- Weist unter der Haube ein reales Array mit 3 Elementen zu (oder mehr, wenn eine Kapazität angegeben ist).
- Initialisiert den Slice-Header (pointer, length, capacity).
- Gibt einen gebrauchsfertigen Slice zurück.
Das Gleiche gilt für eine Map: make(map[string]int) erstellt eine Hashtabelle und weist Speicher für Buckets zu. new(map[string]int) liefert nur eine nil-Map.
Wann sollte man new() im Jahr 2025 verwenden?
Die moderne Praxis in der Go-Community ist: new() wird selten verwendet. Meistens bevorzugen Entwickler Literale mit Adressübernahme: &Type{}. Dies ist lesbarer und erlaubt es, Felder sofort zu setzen.