Menu

Golang Methoden: Value Receiver vs Pointer Receiver erklärt

Eine Methode ist eine Funktion mit einem Receiver. So deklarierst du Methoden, wann du einen Value oder Pointer Receiver nimmst, Method Sets und Interfaces sowie Methoden auf Typen, die keine Structs sind.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Eine Methode deklarieren

Eine Methode ist eine Funktion mit einem Receiver: einem zusätzlichen Parameter vor dem Methodennamen. Der Receiver bindet die Methode an einen Typ.

Der Receiver-Name besteht meist aus ein oder zwei Buchstaben des Typs (c für Circle). Go verwendet kein this oder self, und Styleguides raten von diesen Namen ab.

Da Circle eine Methode String() string hat, erfüllt es fmt.Stringer, und fmt.Println gibt deren Ergebnis statt {2} aus.

Value Receiver und Pointer Receiver

Ein Value Receiver (c Circle) bekommt eine Kopie. Ein Pointer Receiver (c *Circle) bekommt die Adresse, also bleiben Änderungen an Feldern erhalten.

IncByValue kompiliert und bewirkt nichts Sichtbares, ein klassischer Bug. Soll eine Methode ihren Receiver ändern, braucht sie einen Pointer Receiver.

Die Aufrufe zeigen auch die beiden Erleichterungen, die Go dir bietet. c.Inc() auf einer adressierbaren Variablen wird zu (&c).Inc() umgeschrieben, und p.Value() auf einem Pointer zu (*p).Value(). Du schreibst also selten & oder *, nur um eine Methode aufzurufen.

Das automatische & braucht etwas Adressierbares. Ein Map-Element oder ein Composite Literal ist das nicht:

m := map[string]Counter{"a": {}}
m["a"].Inc()    // compile error: cannot call pointer method Inc on Counter
Counter{}.Inc() // same error: a composite literal is not addressable

Speichere Pointer in der Map (map[string]*Counter) oder kopier den Wert heraus, änder ihn und schreib ihn zurück.

Welchen Receiver wählen

Nimm einen Pointer Receiver, wennNimm einen Value Receiver, wenn
die Methode den Receiver ändertder Typ klein ist und von seinen Methoden nie geändert wird (time.Time, ein Punkt)
das Struct groß ist (Kopieren kostet)der Typ eine Map, func oder chan ist (schon eine Referenz)
der Typ einen sync.Mutex oder Ähnliches enthältsich der Wert wie ein primitiver Typ verhalten soll, sicher kopierbar
andere Methoden des Typs schon Pointer nutzen

Die Go-FAQ und die Standardbibliothek folgen einer Faustregel: Sei einheitlich. Braucht eine Methode einen Pointer Receiver, gib allen Methoden Pointer Receiver, damit das Method Set vorhersehbar ist. go vet meldet ein Struct mit einem sync.Mutex, das über einen Value Receiver kopiert wird (passes lock by value).

Method Sets und Interfaces

Am wichtigsten ist der Unterschied bei Interfaces. Das Method Set von T enthält die Methoden mit Value Receiver. Das Method Set von *T enthält die Methoden mit Value Receiver und die mit Pointer Receiver.

Schreibst du in diesem Slice Square{Side: 3} ohne das &, kompiliert es nicht:

cannot use Square{…} (value of struct type Square) as Shape value in array or slice literal: Square does not implement Shape (method Area has pointer receiver)

Der Grund: Ein Interface kann eine nicht adressierbare Kopie eines Square enthalten, also kann Go nicht deren Adresse nehmen, um eine Pointer-Methode aufzurufen. Den Rest der Geschichte erzählt die Seite zu Interfaces.

Methoden auf Typen, die keine Structs sind

Jeder benannte Typ, der in deinem Paket deklariert ist, kann Methoden haben, nicht nur Structs.

Die Grenzen: Der Typ muss im selben Paket definiert sein wie die Methode, und sein zugrunde liegender Typ darf kein Pointer und kein Interface sein. Aus deinem eigenen Paket kannst du int, string oder time.Time keine Methoden hinzufügen. In Go verpackst du den Typ stattdessen in einen neuen benannten Typ. String() auf einem Ganzzahltyp ist auch der Weg, wie Enums eine lesbare Ausgabe bekommen; siehe Enums mit iota.

Method Values und Method Expressions

Eine Methode, die an einen bestimmten Receiver gebunden ist, ist ein Funktionswert:

Ein Method Value mit Value Receiver kopiert den Receiver beim Erzeugen, deshalb sagt f immer noch Ana. Mit Pointer Receiver würde er den Pointer speichern und spätere Änderungen sehen. Method Values sind praktisch für Callbacks: http.HandleFunc("/", srv.handleIndex).

Häufige Fehler

  • Value Receiver bei einer ändernden Methode. Kompiliert und ändert still eine Kopie.
  • Receiver-Arten mischen. Dann ist unklar, ob T oder *T ein Interface erfüllt. Entscheide dich pro Typ für eine Art.
  • Eine Pointer-Methode auf einem nil-Pointer aufrufen. Erlaubt, und die Methode läuft mit einem nil-Receiver. Eine Panic gibt es erst, wenn sie auf ein Feld zugreift. Manche Typen (etwa eine nil-sichere verkettete Liste) nutzen das absichtlich; die meisten rechnen nicht damit.

Häufig gestellte Fragen

Was ist der Unterschied zwischen einer Methode und einer Funktion in Go?

Eine Methode hat einen Receiver, der zwischen func und dem Namen steht: func (c Circle) Area() float64. Du rufst sie auf einem Wert auf (c.Area()), sie gehört zum Method Set des Typs, und über sie erfüllt ein Typ Interfaces. Ansonsten verhält sie sich wie eine Funktion, deren erstes Argument der Receiver ist.

Wann sollte ich in Go einen Pointer Receiver verwenden?

Nimm einen Pointer Receiver, wenn die Methode den Receiver ändern muss, wenn das Struct so groß ist, dass das Kopieren bei jedem Aufruf Verschwendung wäre, oder wenn der Typ etwas enthält, das nicht kopiert werden darf, etwa einen sync.Mutex. Braucht irgendeine Methode eines Typs einen Pointer Receiver, lautet der übliche Rat, der Einheitlichkeit halber allen Methoden Pointer Receiver zu geben.

Warum implementiert mein Typ das Interface nicht, wenn die Methode einen Pointer Receiver hat?

Methoden mit Pointer Receiver gehören zum Method Set von *T, nicht von T. Daher scheitert var s Shape = Square{} mit Square does not implement Shape (method Area has pointer receiver). Weise stattdessen einen Pointer zu: var s Shape = &Square{}.

Kann man in Go Methoden auf eingebauten Typen definieren?

Nicht direkt. Methoden lassen sich nur auf benannten Typen deklarieren, die im selben Paket definiert sind. Definier zuerst einen eigenen Typ, type Celsius float64, und häng dann Methoden daran. Zu int, string oder einem Typ aus einem anderen Paket kannst du keine Methoden hinzufügen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S