Zum Inhalt springen
Go Anfรคnger 25 min

Funktionen & Multi-Return

Funktionen in Go: Parameter, Rueckgabewerte, multiple Returns, benannte Returns und Varianten mit ... Argumenten.

Aktualisiert:
Inhaltsverzeichnis

Funktionen in Go

Funktionen sind einer der beliebtesten Teile der Sprache - besonders wegen mehrfacher Rueckgabewerte, die Goโ€™s Fehlerbehandlung so klar machen.

Grundform

func gruesse(name string) {
    fmt.Printf("Hallo, %s!\n", name)
}

func main() {
    gruesse("Anna")
}
  • func leitet eine Funktion ein
  • camelCase fuer Funktionsnamen
  • Parameter brauchen immer einen Typ - nach dem Namen

Rueckgabewert

Der Rueckgabetyp steht nach den Parametern:

func addiere(a, b int) int {
    return a + b
}

Wenn mehrere Parameter den gleichen Typ haben, muss der Typ nur beim letzten stehen: a, b int = a int, b int.

Mehrere Rueckgabewerte

Goโ€™s Killer-Feature: Funktionen koennen mehrere Werte zurueckgeben. Die Typen stehen in Klammern:

func minMax(zahlen []int) (int, int) {
    min, max := zahlen[0], zahlen[0]
    for _, z := range zahlen {
        if z < min { min = z }
        if z > max { max = z }
    }
    return min, max
}

func main() {
    min, max := minMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
    fmt.Println(min, max) // 1 9
}

Der Standard-Pattern: value, error

Die bei weitem haeufigste Form in Go:

func liesZahl(s string) (int, error) {
    return strconv.Atoi(s)
}

func main() {
    if zahl, err := liesZahl("42"); err == nil {
        fmt.Println("Zahl:", zahl)
    } else {
        fmt.Println("Fehler:", err)
    }
}

Diese value, err-Konvention taucht ueberall in Go-Code auf. Wenn du sie verinnerlichst, wirst du Go-Code sofort lesen koennen.

Benannte Rueckgabewerte

Du kannst Rueckgabewerte benennen. Sie werden am Anfang der Funktion mit ihrem Zero Value initialisiert, und du kannst ein nacktes return benutzen:

func teile(a, b float64) (ergebnis float64, err error) {
    if b == 0 {
        err = fmt.Errorf("Division durch Null")
        return
    }
    ergebnis = a / b
    return
}

Das ist praktisch bei mehreren Return-Pfaden - aber fuer kurze Funktionen lohnt es sich nicht. Halte deine Funktionen klein und verwende explizite Returns.

Variadic Parameter (...)

Funktionen koennen beliebig viele Argumente eines Typs nehmen:

func summe(zahlen ...int) int {
    total := 0
    for _, z := range zahlen {
        total += z
    }
    return total
}

func main() {
    fmt.Println(summe(1, 2, 3))        // 6
    fmt.Println(summe(1, 2, 3, 4, 5))  // 15

    zahlen := []int{10, 20, 30}
    fmt.Println(summe(zahlen...))       // 60 - Slice "entpacken"
}

fmt.Println selbst ist eine variadic Funktion - deshalb kannst du beliebig viele Werte uebergeben.

Funktionen als Werte

Funktionen sind in Go First-Class Values:

func main() {
    addiere := func(a, b int) int {
        return a + b
    }

    fmt.Println(addiere(3, 4)) // 7
}

Du kannst sie an andere Funktionen uebergeben:

func anwenden(f func(int) int, wert int) int {
    return f(wert)
}

func verdopple(n int) int { return n * 2 }

func main() {
    fmt.Println(anwenden(verdopple, 5)) // 10
    fmt.Println(anwenden(func(n int) int { return n * n }, 5)) // 25
}

Closures

Anonyme Funktionen โ€œmerkenโ€ sich Variablen aus ihrem Scope:

func zaehler() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

func main() {
    naechster := zaehler()
    fmt.Println(naechster()) // 1
    fmt.Println(naechster()) // 2
    fmt.Println(naechster()) // 3
}

Jeder Aufruf von zaehler() erzeugt einen eigenen n-Wert - Closures sind kleine, private Zustandsmaschinen.

defer innerhalb von Funktionen

Wir haben defer kurz gesehen - in Funktionen wird es wichtig:

func schreibeProtokoll(datei string, eintrag string) error {
    f, err := os.Create(datei)
    if err != nil {
        return err
    }
    defer f.Close() // wird **garantiert** aufgerufen, egal welches return

    _, err = f.WriteString(eintrag)
    return err
}

defer garantiert Cleanup - keine vergessene Close-Operation, auch bei Fehlern.

Panic und Recover (Ausnahmefaelle)

Go hat keine Exceptions - Fehler werden ueber error zurueckgegeben. Fuer wirklich aussergewoehnliche Situationen gibt es panic und recover:

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Erholung von:", r)
        }
    }()
    panic("etwas ist schiefgelaufen")
}

Aber: Nutze das selten. Der Standard ist return err.

Kleine Stil-Regeln

  • Eine Funktion sollte eine Aufgabe haben. Wenn sie nicht in einen Bildschirm passt, ueberlege Zerlegung.
  • Exportieren (oeffentlich) = grosser Anfangsbuchstabe. GroesseBerechnen ist oeffentlich, groesseBerechnen privat.
  • Kurze Variablen fuer kurze Scopes (i, r, s), beschreibende Namen fuer groessere Scopes.

Zusammenfassung

  • func name(params) (returns) { ... }
  • Parameter brauchen einen Typ, Typ kommt nach dem Namen
  • Mehrere Rueckgabewerte sind das Standardidiom (value, err)
  • Variadic Parameter mit ...Typ
  • Funktionen sind Werte - du kannst sie uebergeben, speichern, in Closures fangen
  • defer garantiert Cleanup am Funktionsende

Im naechsten Kapitel: Slices und Maps - die wichtigsten Datenstrukturen.

Zurรผck zum Go Kurs