Value Receiver vs. Pointer Receiver: Ein unendlicher Duell
\n\nWenn Sie auch nur einmal in Go geschrieben haben, sind Sie sicherlich auf diese Frage gestoßen. Sie öffnen Stack Overflow – und dort tausende Aufrufe, hunderte Kommentare, und jeder zweite Anfänger (und nicht nur der) zerbricht sich den Kopf: "Was soll ich nun wählen? Value Receiver oder Pointer Receiver?"
\n\nSpoiler: Es gibt keine richtige Antwort für alle Fälle. Aber es gibt klare Regeln, die Ihnen in 95% der Fälle helfen, keinen Fehler zu machen. Und heute werden wir sie ein für alle Mal durchgehen.
\n\nIn diesem Artikel werde ich nicht einfach die trockene Dokumentation übersetzen. Wir gehen durch reale Anwendungsfälle, betrachten Performance, Mutabilität, Interfaces und sogar, wie die Go-Community zu den verschiedenen Ansätzen steht. Los geht's!
\n\nWas ist ein Receiver in Go?
\n\nFür diejenigen, die die Sprache gerade erst kennenlernen: Ein Receiver ist ein Parameter, der eine Funktion mit einem Typ verbindet. So werden in Go Methoden implementiert.
\n\ntype User struct {\n Name string\n Age int\n}\n\n// Value receiver\nfunc (u User) Greet() string {\n return "Hallo, ich bin " + u.Name\n}\n\n// Pointer receiver\nfunc (u *User) Birthday() {\n u.Age++\n}\n\n\nIm ersten Fall arbeiten wir mit einer Kopie der Struktur. Im zweiten Fall mit einem Zeiger auf das Original. Es scheint eine Kleinigkeit zu sein. Aber aus dieser Kleinigkeit erwachsen ganze architektonische Entscheidungen.
\n\nWann man den Value Receiver verwendet
\n\nBeginnen wir mit der einfachsten und sichersten Variante.
\n\n1. Die Methode verändert den Empfänger nicht
\n\nDies ist die goldene Regel. Wenn die Methode nur Daten liest und nichts ändert – verwenden Sie den Value Receiver. Das ist sicher, vorhersagbar und verständlich.
\n\ntype Rectangle struct {\n Width, Height float64\n}\n\nfunc (r Rectangle) Area() float64 {\n return r.Width * r.Height\n}\n\n\nNiemand erwartet, dass die Berechnung der Fläche das Rechteck selbst verändert. Der Value Receiver ist hier die ideale Wahl.
\n\n2. Kleine Strukturen
\n\nWenn Ihre Struktur aus ein paar Feldern besteht (wie Rectangle oben), kostet das Kopieren praktisch nichts. Mehr noch, es kann schneller sein als die Arbeit mit einem Zeiger, weil die Daten auf dem Stack liegen, nicht auf dem Heap.
\n\n3. Sicherheit und Unveränderlichkeit
\n\nManchmal möchten Sie bewusst garantieren, dass die Methode die ursprünglichen Daten nicht ändert. Der Value Receiver gibt Ihnen diese Garantie auf Compiler-Ebene.
\n\ntype Config struct {\n Timeout int\n}\n\nfunc (c Config) WithTimeout(t int) Config {\n c.Timeout = t\n return c\n}\n\n\nDies ist ein Muster der funktionalen Programmierung: Die Methode gibt eine neue Kopie zurück, ohne das Original zu berühren. Ideal für unveränderliche Konfigurationen.
\n\nWann man den Pointer Receiver wählt
\n\nUnd hier wird es richtig interessant. Der Pointer Receiver ist ein mächtiges Werkzeug, aber man muss vorsichtig damit umgehen.
\n\n1. Die Methode verändert den Empfänger
\n\nDies ist der offensichtlichste Fall. Wenn Sie SetName, UpdateStatus oder Increment schreiben – brauchen Sie einen Zeiger.
type Counter struct {\n Value int\n}\n\nfunc (c *Counter) Increment() {\n c.Value++\n}\n\n\nOhne Zeiger würden Sie eine Kopie inkrementieren, und das Original bliebe unverändert. Ein klassischer Anfängerfehler.
\n\n2. Große Strukturen
\n\nWenn Ihre Struktur hundert Bytes oder mehr wiegt, ist das Kopieren bei jedem Methodenaufruf teuer. Besonders, wenn die Methode in einer Schleife aufgerufen wird.
\n\ntype LargeData struct {\n Buffer [1024]byte\n Meta map[string]interface{}\n}\n\n// Das bei jedem Aufruf zu kopieren ist schmerzhaft\nfunc (l *LargeData) Process() {\n // Arbeiten mit dem Original\n}\n\n\nHier ist der Pointer Receiver nicht nur eine Bequemlichkeit, sondern eine Performance-Optimierung.
\n\n