Zurück zum Blog

Das Go-Keyword func: Funktionen, Methoden und Closures

So funktioniert das Go-Keyword func: Funktionssignaturen, mehrere Rückgabewerte und benannte Ergebnisse, variadische Funktionen, Value- vs. Pointer-Receiver, Funktionswerte, Closures und Schleifenvariablen, Functional Options, generische Funktionen, main und init.

Das Go-Keyword func: Funktionen, Methoden und Closures

Alles, was in einem Go-Programm etwas tut, deklarierst du mit dem Keyword func. func parse(body []byte) (Request, error) ist eine Funktion. func (s *Server) Start() error ist eine Methode, weil vor ihrem Namen ein Receiver steht. func(w http.ResponseWriter, r *http.Request) { ... } ohne Namen ist ein Funktionsliteral. Es kann Variablen aus seiner Umgebung einfangen, und so entstehen in Go Closures. Ein einziges Keyword deckt alle drei Fälle ab, und die Regeln für Parameter und Ergebnisse sind überall dieselben (Go spec).

Kurzfassung

  • Eine Funktionsdeklaration hat die Form func name(params) results { body }. Aufeinanderfolgende Parameter desselben Typs teilen sich den Typ, wie in clamp(n, lo, hi int).
  • Go hat weder Overloading noch Default-Argumente. Zwei Funktionen mit demselben Namen scheitern mit redeclared in this block. Nimm einen anderen Namen, ein Options-Struct oder Functional Options.
  • Funktionen können mehrere Werte zurückgeben. Über das Paar (T, error) meldet Go Fehler, und _ verwirft ein Ergebnis, das du nicht brauchst.
  • Benannte Ergebnisse dokumentieren, was eine Funktion zurückgibt, und erlauben einer per defer aufgerufenen Closure, err kurz vor dem Verlassen der Funktion zu ergänzen.
  • ...T macht eine Funktion variadisch. Innerhalb der Funktion ist der Parameter ein []T, und parts... breitet einen Slice im Aufruf aus.
  • Eine Methode ist eine Funktion mit Receiver. Verwende einen Pointer-Receiver, wenn die Methode den Wert ändert oder der Typ einen sync.Mutex enthält.
  • Eine Methode mit Pointer-Receiver gehört nicht zur Methodenmenge des Value-Typs. API{} erfüllt http.Handler also nicht, wenn nur *API die Methode ServeHTTP hat.
  • Funktionen sind Werte. Du kannst sie speichern, an slices.SortFunc oder http.HandleFunc übergeben und nur mit nil vergleichen.
  • Ein Funktionsliteral fängt die Variablen selbst ein, keine Kopien. Seit Go 1.22 bekommt jede Schleifeniteration ihre eigene Schleifenvariable, also sehen Goroutines, die in einer Schleife gestartet werden, den Wert, den sie erwarten.
  • Functional Options (NewServer(addr, opts ...Option)) geben Konstruktoren optionale Einstellungen. Seit Go 1.27 können Methoden eigene Typparameter deklarieren, etwa func (c *Client) GetJSON[T any](path string) (T, error).

Wie deklariert man eine Funktion in Go?

Schreib func, einen Namen, die Parameterliste und die Ergebnistypen. Ein Token-Parser ist eine typische kleine Funktion:

example.gogo
package main

import (
	"errors"
	"fmt"
	"strings"
)

func parseBearer(header string) (string, error) {
	token, ok := strings.CutPrefix(header, "Bearer ")
	if !ok || token == "" {
		return "", errors.New("missing bearer token")
	}
	return token, nil
}

func clamp(n, lo, hi int) int {
	return max(lo, min(n, hi))
}

func main() {
	token, err := parseBearer("Bearer eyJhbGciOi")
	fmt.Println(token, err)

	_, err = parseBearer("Basic dXNlcjpwYXNz")
	fmt.Println(err)

	fmt.Println(clamp(250, 1, 100))
}
example.texttext
eyJhbGciOi <nil>
missing bearer token
100

Typen stehen hinter den Namen, bei Parametern genauso wie bei Ergebnissen. Wenn mehrere Parameter hintereinander denselben Typ haben, schreibst du ihn nur einmal. clamp(n, lo, hi int) nimmt also drei ints. Ein einzelnes unbenanntes Ergebnis braucht keine Klammern. Zwei oder mehr Ergebnisse schon.

Für Funktionsnamen gilt dieselbe Sichtbarkeitsregel wie für jeden anderen Bezeichner in Go. ParseBearer wäre exportiert und aus anderen Packages aufrufbar, während parseBearer in seinem eigenen Package bleibt. Das Go-Keyword package erklärt exportierte Namen genauer.

Gibt es in Go Function Overloading oder Default-Argumente?

Weder noch. Ein Package kann nur eine Funktion mit einem bestimmten Namen haben, egal wie ihre Parameter aussehen:

example.gogo
func dial(addr string) error { return nil }

func dial(addr string, timeout time.Duration) error { return nil }
example.texttext
./main.go:7:6: dial redeclared in this block
	./main.go:5:6: other declaration of dial

Default-Werte scheitern schon im Parser, bevor die Typprüfung überhaupt beginnt:

example.texttext
./main.go:5:46: syntax error: unexpected = in parameter list; possibly missing comma or )

Die Standardbibliothek zeigt, was Go stattdessen macht. net.Dial und net.DialTimeout sind zwei Namen für zwei Verhaltensweisen. http.Server ist ein Struct, dessen Nullwert-Felder „nimm den Default“ bedeuten, also setzt du nur die Felder, die dich interessieren. Für Konstruktoren mit vielen optionalen Einstellungen verwendet Go-Code Functional Options, dazu weiter unten mehr. An jeder Aufrufstelle verrät dir der Name, welche Funktion läuft.

Wie funktionieren mehrere Rückgabewerte in Go?

Eine Funktion kann beliebig viele Werte zurückgeben, und der Aufrufer nimmt sie mit einer Mehrfachzuweisung entgegen. Die häufigste Form ist ein Ergebnis plus ein error, wie bei parseBearer oben. Der Aufrufer prüft den Fehler, bevor er das Ergebnis anfasst, und der Compiler stellt sicher, dass ein Aufruf mit zwei Ergebnissen nicht dort verwendet wird, wo ein einzelner Wert erwartet wird.

Wenn du einen der Werte nicht brauchst, weist du ihn dem Blank Identifier _ zu. Ganz weglassen kannst du ihn nicht. token := parseBearer(h) scheitert mit assignment mismatch: 1 variable but parseBearer returns 2 values.

Wann solltest du benannte Rückgabeparameter verwenden?

Benannte Ergebnisse geben jedem Ergebnis einen Namen und deklarieren es als Variable, die mit ihrem Nullwert beginnt. Ein return ohne Werte, ein sogenannter Bare Return, gibt dann zurück, was in diesen Variablen steht:

example.gogo
func splitHostPort(addr string) (host, port string) {
	for i := len(addr) - 1; i >= 0; i-- {
		if addr[i] == ':' {
			host, port = addr[:i], addr[i+1:]
			return
		}
	}
	host = addr
	return
}

splitHostPort("api.levelupgo.dev:443") gibt api.levelupgo.dev und 443 zurück. Die Namen helfen hier, weil zwei string-Ergebnisse sonst nicht verraten, welches wofür steht. Die Go Code Review Comments empfehlen Bare Returns nur in kurzen Funktionen wie dieser. In einer Funktion mit 60 Zeilen sieht man einem nackten return nicht mehr an, was zurückkommt.

Am meisten bringen benannte Ergebnisse, wenn eine per defer aufgerufene Funktion den Fehler bearbeiten muss, bevor der Aufrufer ihn sieht:

example.gogo
func loadConfig(path string) (cfg Config, err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("load config %s: %w", path, err)
		}
	}()

	data, err := os.ReadFile(path)
	if err != nil {
		return Config{}, err
	}
	err = json.Unmarshal(data, &cfg)
	return cfg, err
}
example.texttext
load config /etc/levelupgo/config.json: open /etc/levelupgo/config.json: no such file or directory

Jedes return weist seine Werte zuerst cfg und err zu, und danach läuft die per defer aufgerufene Funktion. Weil err ein benanntes Ergebnis ist, kann die Closure es einmal für jeden Ausstiegspunkt wrappen. Ohne den Namen kommt die Funktion in defer nicht an den Rückgabewert heran.

Was sind variadische Funktionen in Go?

Ist der letzte Parameter als ...T geschrieben, nimmt er null oder mehr Argumente vom Typ T an. Innerhalb der Funktion ist er ein gewöhnlicher []T:

example.gogo
func buildURL(base string, segments ...string) string {
	return strings.TrimSuffix(base, "/") + "/" + strings.Join(segments, "/")
}

func main() {
	fmt.Println(buildURL("https://api.levelupgo.dev/", "v1", "users", "42"))
	fmt.Println(buildURL("https://api.levelupgo.dev"))

	parts := []string{"v1", "orders", "1001"}
	fmt.Println(buildURL("https://api.levelupgo.dev", parts...))
}
example.texttext
https://api.levelupgo.dev/v1/users/42
https://api.levelupgo.dev/
https://api.levelupgo.dev/v1/orders/1001

parts... übergibt einen vorhandenen Slice als variadisches Argument, ohne ihn zu kopieren. Die beiden Formen lassen sich nicht mischen, deshalb scheitert buildURL(base, "v2", parts...):

example.texttext
./main.go:18:58: too many arguments in call to buildURL
	have (string, string, []string...)
	want (string, ...string)

Die Standardbibliothek steckt voller solcher Funktionen. fmt.Println(a ...any), append(s, elems...), errors.Join(errs ...error) und die Schlüssel-Wert-Paare in slog.Info(msg, args ...any) haben alle diese Form.

Wie funktionieren Methoden in Go?

Eine Methode ist eine Funktion mit Receiver, der in Klammern zwischen func und dem Namen steht. Receiver sind meistens Structs, und das Go-Keyword struct behandelt sie von Feldern bis Embedding. Der Receiver kann der Typ selbst oder ein Pointer darauf sein, und diese Wahl entscheidet, ob die Methode mit dem Wert des Aufrufers oder mit einer Kopie arbeitet. Ein Rate Limiter mit Value-Receiver zeigt den Unterschied:

example.gogo
type RateLimiter struct {
	limit int
	used  int
}

func (r RateLimiter) Allow() bool {
	if r.used >= r.limit {
		return false
	}
	r.used++
	return true
}

func main() {
	limiter := RateLimiter{limit: 2}
	for range 4 {
		fmt.Print(limiter.Allow(), " ")
	}
	fmt.Println()
}
example.texttext
true true true true

Der Limiter blockiert nie etwas. Jeder Aufruf bekommt eine frische Kopie von limiter, erhöht used auf der Kopie und wirft sie weg. Änderst du den Receiver in func (r *RateLimiter) Allow() bool, aktualisiert die Methode das Original, und die Ausgabe wird true true false false. Du musst nicht (&limiter).Allow() schreiben. Go nimmt die Adresse für dich, wenn die Variable adressierbar ist.

Wann solltest du einen Pointer-Receiver verwenden?

Verwende einen Pointer-Receiver, wenn einer dieser Punkte zutrifft:

  • Die Methode ändert den Receiver, wie Allow oben.
  • Der Typ enthält einen sync.Mutex oder einen anderen Wert, der nicht kopiert werden darf.
  • Das Struct ist groß, sodass eine Kopie bei jedem Aufruf mehr kostet als der Umweg über einen Pointer.
Value-Receiver func (r T)Pointer-Receiver func (r *T)
Ändert den Wert des AufrufersNein, arbeitet auf einer KopieJa
Sicher mit einem sync.Mutex-FeldNein, das Lock wird kopiertJa
Auf einer T-Variable aufrufbarJaJa, Go nimmt die Adresse
In der Methodenmenge von TJaNein
In der Methodenmenge von *TJaJa

Der Mutex-Fall ist ein Bug, den go vet findet:

example.gogo
type SessionCache struct {
	mu       sync.Mutex
	sessions map[string]string
}

func (c SessionCache) Get(token string) (string, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	user, ok := c.sessions[token]
	return user, ok
}
example.texttext
main.go:13:9: Get passes lock by value: app.SessionCache contains sync.Mutex

Jeder Aufruf sperrt seine eigene Kopie des Mutex. Zwei Goroutines, die Get aufrufen, warten also nie aufeinander, und das Lock schützt nichts. Kleine unveränderliche Typen wie time.Time oder eine UserID sind mit Value-Receivern in Ordnung. Die Code Review Comments raten außerdem davon ab, beide Arten auf einem Typ zu mischen. Braucht eine Methode einen Pointer-Receiver, bekommen ihn alle (Go wiki).

Warum implementiert mein Typ ein Interface nicht?

Eine häufige Ursache ist ein Pointer-Receiver. Die Methodenmenge eines Value-Typs T enthält nur die Methoden mit einem T-Receiver. Die Methodenmenge von *T enthält beide Arten. Wenn ServeHTTP also einen Pointer-Receiver hat, ist nur *API ein http.Handler:

example.gogo
type API struct {
	version string
}

func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, a.version)
}

func main() {
	var h http.Handler = API{version: "v1"}
	_ = h
}
example.texttext
./main.go:17:23: cannot use API{…} (value of struct type API) as http.Handler value in variable declaration: API does not implement http.Handler (method ServeHTTP has pointer receiver)

Mit &API{version: "v1"} ist das Problem behoben. Die Regel existiert, weil ein Interface eine Kopie eines Werts halten kann, und eine Pointer-Methode, die auf dieser Kopie aufgerufen wird, würde etwas ändern, das der Aufrufer nie sieht. Das Go-Keyword var zeigt das Muster var _ http.Handler = (*T)(nil), das genau das zur Compilezeit prüft, und das Go-Keyword interface behandelt Methodenmengen und implizites Erfüllen von Interfaces ausführlich.

Was sind Method Values und Method Expressions?

srv.health ohne Klammern ist ein Method Value. Das ist eine Funktion, an die der Receiver schon gebunden ist, du kannst sie also direkt an alles übergeben, was eine gewöhnliche Funktion erwartet:

example.gogo
type Server struct {
	version string
}

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "ok %s\n", s.version)
}

func main() {
	srv := &Server{version: "1.4.2"}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", srv.health)

	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, httptest.NewRequest("GET", "/health", nil))
	fmt.Print(rec.Body.String())

	handle := (*Server).health
	fmt.Printf("%T\n", handle)
}
example.texttext
ok 1.4.2
func(*main.Server, http.ResponseWriter, *http.Request)

So registrieren viele Go-Server ihre Routen. Handler sind Methoden auf einem Struct, das Datenbank, Logger und Config hält, und mux.HandleFunc bekommt die gebundenen Method Values. (*Server).health ist eine Method Expression. Sie macht aus dem Receiver einen gewöhnlichen ersten Parameter. Das ist nützlich, wenn eine Tabelle zur Laufzeit auswählt, welche Methode aufgerufen wird.

Was sind Funktionswerte und Funktionstypen?

Funktionen sind in Go Werte. Du kannst sie Variablen zuweisen, in Struct-Feldern und Maps speichern, als Argumente übergeben und aus anderen Funktionen zurückgeben. Der Typ eines Funktionswerts ist seine Signatur:

example.gogo
type Order struct {
	ID    int64
	Total int64
}

func main() {
	orders := []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}, {ID: 3, Total: 8900}}

	slices.SortFunc(orders, func(a, b Order) int {
		return cmp.Compare(b.Total, a.Total)
	})
	fmt.Println(orders)

	bigOrder := func(o Order) bool { return o.Total > 5000 }
	fmt.Println(slices.IndexFunc(orders, bigOrder))
	fmt.Printf("%T\n", bigOrder)
}
example.texttext
[{3 8900} {1 4999} {2 1250}]
0
func(main.Order) bool

slices.SortFunc weiß nicht, wie deine Structs sortiert werden sollen, also übergibst du eine Vergleichsfunktion. Dieselbe Idee zieht sich durch die ganze Standardbibliothek: http.HandleFunc nimmt eine Handler-Funktion, strings.FieldsFunc einen Test für Trennzeichen und sync.OnceValue die Funktion, die einmal laufen soll.

Der Nullwert eines Funktionstyps ist nil, und der Aufruf einer nil-Funktion löst eine Panic aus. Das ist auch der einzige Vergleich, den Go erlaubt:

example.texttext
./main.go:9:5: invalid operation: health == ready (func can only be compared to nil)

Weil du zwei Funktionen nicht vergleichen kannst, können sie auch keine Map-Schlüssel sein. Wenn du eine Menge von Handlern brauchst, verwende stattdessen den Namen als Schlüssel.

Eine Signatur kann mit type auch einen eigenen Namen bekommen, und ein benannter Funktionstyp kann Methoden haben. So macht http.HandlerFunc aus einer gewöhnlichen Funktion einen http.Handler, und das Go-Keyword type geht das zusammen mit einem Beispiel für eine RetryPolicy durch.

Wie funktionieren Closures in Go?

Ein Funktionsliteral kann Variablen aus der umgebenden Funktion verwenden. Es kopiert sie nicht. Es fängt die Variablen selbst ein, also sind Änderungen auf beiden Seiten für beide sichtbar. Ein Retry-Helper zeigt das:

example.gogo
var errUnavailable = errors.New("503 service unavailable")

func retry(attempts int, fn func() error) error {
	var err error
	for range attempts {
		if err = fn(); err == nil {
			return nil
		}
	}
	return err
}

func main() {
	calls := 0
	err := retry(3, func() error {
		calls++
		return errUnavailable
	})
	fmt.Println(calls, err)
}
example.texttext
3 503 service unavailable

calls lebt in main, aber das Literal erhöht die Variable, und main sieht danach 3. Die eingefangene Variable bleibt am Leben, solange irgendeine Closure auf sie verweist, auch nachdem die umgebende Funktion zurückgekehrt ist. Der Compiler verschiebt solche Variablen bei Bedarf auf den Heap.

HTTP-Middleware gehört zu den häufigsten Closures in Go-Code. Der zurückgegebene Handler behält logger und next aus dem Aufruf, der ihn gebaut hat:

example.gogo
func logRequests(logger *slog.Logger, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		next.ServeHTTP(w, r)
		logger.Info("request", "method", r.Method, "path", r.URL.Path)
	})
}

Wrappst du damit einen Handler und schickst DELETE /sessions/42 hindurch, wird level=INFO msg=request method=DELETE path=/sessions/42 geloggt. Jeder Aufruf von logRequests erzeugt eine eigene Closure mit ihrem eigenen next, du kannst also jede Route mit einem anderen Handler wrappen. Den kompletten Produktions-Stack aus solchen Closures, inklusive Reihenfolge, Panic-Recovery und Tests, zeigt Go-Middleware: Best Practices für die Produktion.

Außerdem wirst du oft zwei Kurzformen sehen. defer func() { ... }() führt eine Closure aus, wenn die Funktion zurückkehrt. So ergänzt das Beispiel loadConfig seinen Fehler, und so wird recover aufgerufen. go func() { ... }() startet eine Closure in einer neuen Goroutine. Die abschließenden () rufen das Literal auf. Lässt du sie weg, beschwert sich der Compiler, dass der Ausdruck in defer oder go ein Funktionsaufruf sein muss.

Hat Go 1.22 Closures in Schleifen repariert?

Ja. Vor Go 1.22 hatte eine for-Schleife eine einzige Schleifenvariable, die sich alle Iterationen teilten, und Closures fingen genau diese eine Variable ein. Dieser Health Checker startete drei Goroutines, die oft alle die letzte URL ausgaben:

example.gogo
urls := []string{"/health", "/ready", "/metrics"}

var wg sync.WaitGroup
for _, url := range urls {
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("checking", url)
	}()
}
wg.Wait()

Mit go 1.21 in der go.mod gab in drei separaten Läufen jede Zeile checking /metrics aus, und go vet warnte mit loop variable url captured by func literal. Mit go 1.22 oder neuer hat jede Iteration ihre eigene url, und derselbe Code prüft alle drei Pfade in der Reihenfolge, in der die Goroutines fertig werden (Go blog). Die Änderung hängt an der go-Zeile in der go.mod, ein altes Modul behält also das alte Verhalten, bis du die Version anhebst. Die alte Kopie url := url in der Schleife braucht man nicht mehr, und der Modernizer forvar in go fix entfernt sie. Das Go-Keyword for behandelt die übrigen Regeln für Schleifenvariablen, etwa warum eine Änderung am range-Wert den Slice nicht ändert.

Was sind Functional Options in Go?

Functional Options kombinieren variadische Parameter und Closures, um einem Konstruktor optionale Einstellungen zu geben, ohne Overloading und ohne Default-Argumente. Jede Option ist eine Funktion, die den Wert ändert, der gerade gebaut wird:

example.gogo
type Server struct {
	addr         string
	readTimeout  time.Duration
	maxBodyBytes int64
	logger       *slog.Logger
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
	return func(s *Server) {
		s.readTimeout = d
	}
}

func WithMaxBodyBytes(n int64) Option {
	return func(s *Server) {
		s.maxBodyBytes = n
	}
}

func NewServer(addr string, opts ...Option) *Server {
	s := &Server{
		addr:         addr,
		readTimeout:  5 * time.Second,
		maxBodyBytes: 1 << 20,
		logger:       slog.Default(),
	}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

func main() {
	a := NewServer(":8080")
	b := NewServer(":8080", WithReadTimeout(30*time.Second), WithMaxBodyBytes(10<<20))
	fmt.Println(a.readTimeout, a.maxBodyBytes)
	fmt.Println(b.readTimeout, b.maxBodyBytes)
}
example.texttext
5s 1048576
30s 10485760

NewServer(":8080") bekommt alle Defaults, und Aufrufer nennen nur, was sie ändern. Eine später hinzugefügte Option macht keinen bestehenden Aufruf kaputt. grpc.NewServer(opts ...ServerOption) von gRPC und viele Datenbank-Clients verwenden dieses Muster. Für einen Typ mit zwei oder drei Einstellungen ist ein einfaches Config-Struct schlichter und leichter zu lesen. Greif also zu Options, wenn die Liste der Einstellungen immer weiter wächst.

Wie funktionieren generische Funktionen in Go?

Eine Funktion kann zwischen ihrem Namen und ihren Parametern Typparameter in eckigen Klammern deklarieren. Map funktioniert für jeden Eingabe- und Ausgabetyp:

example.gogo
func Map[T, U any](items []T, fn func(T) U) []U {
	out := make([]U, 0, len(items))
	for _, item := range items {
		out = append(out, fn(item))
	}
	return out
}

emails := Map(users, func(u User) string { return u.Email })

Der Compiler leitet aus den Argumenten T als User und U als string ab, also braucht der Aufruf keine eckigen Klammern. Die Packages slices, maps und cmp der Standardbibliothek bestehen aus generischen Funktionen wie dieser.

Bis Go 1.27 konnten Methoden keine eigenen Typparameter deklarieren. Sie konnten die Typparameter ihres Receiver-Typs verwenden, mehr nicht. Mit Go 1.27 fällt diese Grenze weg, ein Client kann also eine generische Methode haben, die in den Typ dekodiert, den der Aufrufer verlangt (Go 1.27 Release Notes):

example.gogo
type Client struct {
	baseURL string
	http    *http.Client
}

func (c *Client) GetJSON[T any](path string) (T, error) {
	var out T
	resp, err := c.http.Get(c.baseURL + path)
	if err != nil {
		return out, err
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&out)
	return out, err
}

u, err := c.GetJSON[User]("/users/1")

Gegen einen Testserver, der einen User zurückgibt, gibt das {1 [email protected]} <nil> aus. Das Typargument muss hier ausgeschrieben werden, weil nichts in "/users/1" dem Compiler verrät, was T ist. Was ist neu in Go 1.27 behandelt generische Methoden und ihre Grenzen, auch warum sie keine Interface-Methoden erfüllen können.

Was sind main und init in Go?

Zwei Funktionsnamen sind besonders. func main() in package main ist der Startpunkt eines Programms, und das Programm endet, wenn die Funktion zurückkehrt. Sie nimmt keine Argumente und gibt nichts zurück:

example.texttext
./main.go:9:6: func main must have no arguments and no return values

Kommandozeilenargumente kommen aus os.Args oder dem Package flag, und der Exit-Code aus os.Exit. Das Go-Keyword package behandelt package main ausführlich.

func init() läuft automatisch, nachdem die Variablen des Packages initialisiert wurden und bevor main startet. Ein Package kann mehrere init-Funktionen haben, sogar in einer Datei, und sie laufen in der Reihenfolge, in der sie im Code stehen. Selbst aufrufen kannst du sie nicht. init() in deinem eigenen Code scheitert mit undefined: init. Meistens wird init für Registrierungen verwendet, etwa wenn sich ein Datenbanktreiber bei database/sql anmeldet. Das Go-Keyword var erklärt die Initialisierungsreihenfolge und wann eine var auf Package-Ebene die klarere Wahl ist.

Wo LevelUpGo ins Spiel kommt

LevelUpGo bringt dir Go mit Übungen bei, die echten Go-Code im Browser ausführen. Go Basics führt Funktionen, mehrere Rückgabewerte und Fehler als Werte ein. Go Language Deep Dives geht weiter mit variadischen Funktionen, benannten Ergebnissen, panic und recover. Der Training Ground bietet kurze, eigenständige Übungen zu Signaturen, Methoden und Closures, die du auch ohne Kurs machen kannst. Die anderen 24 reservierten Wörter findest du in Go Keywords: Alle 25 erklärt.

Häufig gestellte Fragen

Ist func ein Keyword in Go?

Ja. func ist eines der 25 reservierten Keywords von Go, du kannst es also nicht als Namen verwenden. Es deklariert Funktionen und Methoden und leitet Funktionsliterale und Funktionstypen ein. Go-Code, der eine Variable mit einer Funktion braucht, nennt sie meistens fn.

Unterstützt Go Function Overloading oder Default-Parameter?

Weder noch. Jeder Name darf pro Package nur einmal deklariert werden, ein zweites func dial(...) scheitert also mit dial redeclared in this block, und ein Default-Wert in einer Parameterliste ist ein Syntaxfehler. Go verwendet unterschiedliche Namen wie net.Dial und net.DialTimeout, ein Config-Struct, dessen Nullwert-Felder „nimm den Default“ bedeuten, wie http.Server, oder Functional Options.

Sollte ich in Go einen Value- oder Pointer-Receiver verwenden?

Verwende einen Pointer-Receiver, wenn die Methode den Receiver ändert, wenn der Typ einen sync.Mutex oder etwas Ähnliches enthält oder wenn das Struct groß ist. Verwende einen Value-Receiver für kleine Typen, die sich wie Werte verhalten, etwa time.Time. Halte alle Methoden eines Typs einheitlich. Denk daran, dass nur *T die Pointer-Methoden in seiner Methodenmenge hat. Das spielt eine Rolle, sobald der Typ ein Interface erfüllen soll.

Was ist func() in Go?

func() ist der Typ einer Funktion, die keine Argumente nimmt und nichts zurückgibt. Jede Signatur, die ohne Namen geschrieben wird, ist ein Funktionstyp, etwa func(context.Context) error. Du verwendest solche Typen für Callbacks, Struct-Felder und Parameter wie sync.OnceFunc(f func()). func() { ... } mit einem Rumpf ist ein Funktionsliteral dieses Typs.

Kann man in Go Funktionen vergleichen?

Nur mit nil. health == ready scheitert mit func can only be compared to nil, und aus demselben Grund kann ein Funktionstyp kein Map-Schlüssel sein. Wenn du Handler nachschlagen musst, leg sie in einer map[string]http.HandlerFunc mit dem Namen als Schlüssel.

Quellen

Schreib Go wie ein Senior Engineer

Interaktive Lektionen im Browser. Die ersten sind kostenlos.

Kostenlose Lektion testenOder kostenloses Konto erstellen