Eine Funktion deklarieren
Eine Funktionsdeklaration besteht aus func, einem Namen, den Parametern, dem Ergebnistyp und einem Rumpf. Der Typ steht hinter dem Parameternamen, nicht davor.
add nimmt zwei int-Werte und gibt ein int zurück. greet gibt nichts zurück, also hat seine Signatur keinen Ergebnistyp, und der Rumpf braucht kein return.
Eine Funktion, die ein Ergebnis deklariert, muss mit einer abschließenden Anweisung enden. Kann ein Pfad die schließende Klammer erreichen, ohne zurückzukehren, bricht der Compiler mit missing return ab.
Parameter mit gemeinsamem Typ
Aufeinanderfolgende Parameter desselben Typs können sich einen Typnamen teilen. func add(a, b int) int bedeutet genau dasselbe wie func add(a int, b int) int. Mischen ist auch erlaubt:
func scale(x, y float64, factor int) (float64, float64)
Hier sind x und y vom Typ float64 und factor ist ein int.
Rückgabewerte
Eine Funktion kann null, einen oder mehrere Werte zurückgeben. Mehrere Ergebnisse stehen in Klammern, und der Aufrufer empfängt sie mit einer Mehrfachzuweisung:
Das gibt 3 2 aus. Das Paar (value, error) ist in Go der häufigste Einsatz dieser Möglichkeit und hat eine eigene Seite zu mehreren Rückgabewerten.
Argumente werden kopiert
Go übergibt jedes Argument als Wert. Die Funktion arbeitet auf einer Kopie, eine Zuweisung an einen Parameter ändert also nie die Variable des Aufrufers.
Die Ausgabe ist:
{1 2}
{100 2}
[99 2 3]
Die dritte Zeile überrascht viele. Ein Slice-Wert ist ein kleiner Header (Pointer, Länge, Kapazität). Die Kopie zeigt auf dasselbe Backing-Array, also schreibt s[0] in das Element des Aufrufers. Aber append in der Funktion kann ein neues Array anlegen, und den neuen Header sieht der Aufrufer nie. Wenn eine Funktion einen Slice wachsen lässt, gib den neuen Slice zurück. Dieselbe Logik gilt für Maps: Die Funktion kann Schlüssel hinzufügen, die der Aufrufer sieht, weil der Map-Wert auf geteilte Daten verweist.
Nimm einen Pointer-Parameter, wenn die Funktion die Variable des Aufrufers ändern muss oder wenn der Wert ein großes Struct ist, das du nicht bei jedem Aufruf kopieren willst.
Funktionen sind Werte
Eine Funktion hat einen Typ, geschrieben ohne den Namen: func(int, int) int. Du kannst eine Funktion in einer Variablen speichern, in eine Map oder einen Slice packen und an eine andere Funktion übergeben.
square ist eine anonyme Funktion, die einer Variablen zugewiesen ist. Anonyme Funktionen können außerdem Variablen aus dem umgebenden Scope lesen und ändern, und das macht sie zu Closures.
Ein benannter Funktionstyp macht Signaturen lesbarer, wenn dieselbe Form an vielen Stellen vorkommt:
type Transform func(int) int
func apply(xs []int, f Transform) []int
Der Nullwert eines Funktionstyps ist nil. Der Aufruf einer nil-Funktion löst eine Panic mit invalid memory address or nil pointer dereference aus, also prüf optionale Callbacks vor dem Aufruf. Funktionswerte lassen sich nur mit nil vergleichen, nie miteinander.
Rekursion
Eine Funktion kann sich selbst aufrufen. Go hat keine Tail-Call-Optimierung, aber die Stacks von Goroutinen wachsen bei Bedarf, also sind gewöhnliche Rekursionstiefen kein Problem.
for i := range 6 zählt von 0 bis 5 und braucht Go 1.22 oder neuer.
Was Go-Funktionen nicht haben
- Keine Überladung. Zwei Funktionen in einem Paket können sich keinen Namen teilen, auch nicht mit unterschiedlichen Parametern.
- Keine Standardwerte oder benannten Argumente. Jeder Parameter wird bei jedem Aufruf in Reihenfolge übergeben. Ein Config-Struct liefert dir benannte, optionale Felder:
NewServer(Config{Port: 8080}). - Keine verschachtelten benannten Funktionen. Innerhalb eines Funktionsrumpfs kannst du nur anonyme Funktionen deklarieren, meist einer Variablen zugewiesen.
Argumentlisten variabler Länge gibt es über variadische Funktionen.
Exportierte und nicht exportierte Funktionen
Eine Funktion, deren Name mit einem Großbuchstaben beginnt (Parse), ist exportiert und aus anderen Paketen aufrufbar. Ein kleingeschriebener Name (parse) ist nur im eigenen Paket sichtbar. Es gibt keine Schlüsselwörter public oder private; die Großschreibung ist die ganze Regel.
Häufige Fehler
- Erwarten, dass eine Parameteränderung beim Aufrufer ankommt. Eine Zuweisung an einen Parameter ändert nur die Kopie. Gib den neuen Wert zurück oder nimm einen Pointer.
- In einer Funktion anhängen und das Ergebnis ignorieren.
func add(s []int) { s = append(s, 1) }wirkt sich nicht auf die Länge des Slices beim Aufrufer aus. Schreibfunc add(s []int) []intund nutzes = add(s). - Ungenutzte Ergebnisse. Go erlaubt es, einen Rückgabewert zu ignorieren.
go vetmeldet einen verworfenen Fehler nicht, Linter wieerrcheck(Teil vongolangci-lint) schon. Nimm_ =nur, wenn das Ignorieren eine bewusste Entscheidung ist.
Häufig gestellte Fragen
Wie definiert man eine Funktion in Go?
Mit dem Schlüsselwort func, einem Namen, einer Parameterliste mit den Typen hinter den Namen und dem Ergebnistyp: func add(a, b int) int { return a + b }. Eine Funktion ohne Ergebnis lässt den Typ weg, und eine Funktion mit mehreren Ergebnissen listet sie in Klammern auf: func divmod(a, b int) (int, int).
Übergibt Go Argumente als Wert oder als Referenz?
Immer als Wert. Die Funktion bekommt eine Kopie jedes Arguments. Bei einem int oder einem Struct ist diese Kopie unabhängig von der Variablen des Aufrufers. Slices, Maps, Channels und Pointer sind kleine Werte, die auf geteilte Daten verweisen: Änderungen an den Elementen, auf die sie zeigen, sieht der Aufrufer, eine Neuzuweisung des Parameters selbst dagegen nicht. Übergib einen Pointer (*T), wenn die Funktion die Variable des Aufrufers ändern muss.
Kann man in Go eine Funktion als Parameter übergeben?
Ja. Funktionen sind Werte mit einem Typ wie func(int) int. Deklarier einen Parameter dieses Typs und übergib jede Funktion mit passender Signatur, benannt oder anonym: func apply(xs []int, f func(int) int).
Unterstützt Go Funktionsüberladung oder Standardparameter?
Nein. Jeder Funktionsname in einem Paket muss eindeutig sein, und jeder Parameter muss bei jedem Aufruf übergeben werden. Übliche Ersatzlösungen sind unterschiedliche Namen (NewServer, NewServerWithTLS), ein Config-Struct, variadische Parameter oder Functional Options.