Menu

Funkcje w Go (Golang): deklarowanie, wywoływanie i przekazywanie

Jak deklarować i wywoływać funkcje w Go: parametry, wspólne typy parametrów, wartości zwracane, przekazywanie przez wartość oraz funkcje jako wartości, które można przechowywać i przekazywać dalej.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Deklarowanie funkcji

Deklaracja funkcji to func, nazwa, parametry, typ wyniku i ciało. Typ zapisuje się po nazwie parametru, a nie przed nią.

add przyjmuje dwie wartości int i zwraca int. greet nic nie zwraca, więc jej sygnatura nie ma typu wyniku, a ciało nie potrzebuje return.

Funkcja, która deklaruje wynik, musi kończyć się instrukcją kończącą. Jeśli jakaś ścieżka może dojść do zamykającego nawiasu klamrowego bez zwrócenia wartości, kompilator zatrzymuje się z błędem missing return.

Parametry o wspólnym typie

Kolejne parametry tego samego typu mogą dzielić jedną nazwę typu. func add(a, b int) int znaczy dokładnie to samo co func add(a int, b int) int. Można też mieszać:

func scale(x, y float64, factor int) (float64, float64)

Tutaj x i y są typu float64, a factor to int.

Wartości zwracane

Funkcja może zwrócić zero, jedną lub kilka wartości. Kilka wyników zapisuje się w nawiasach, a wywołujący odbiera je przypisaniem wielokrotnym:

To wypisuje 3 2. Para (value, error) to najczęstsze zastosowanie tej funkcji języka w Go i ma własną stronę o wielu wartościach zwracanych.

Argumenty są kopiowane

Go przekazuje każdy argument przez wartość. Funkcja pracuje na kopii, więc przypisanie do parametru nigdy nie zmienia zmiennej wywołującego.

Wynik:

{1 2}
{100 2}
[99 2 3]

Trzecia linia zaskakuje wiele osób. Wartość slice'a to mały nagłówek (wskaźnik, długość, pojemność). Kopia wskazuje na tę samą tablicę bazową, więc zapis s[0] zmienia element wywołującego. Ale append wewnątrz funkcji może zaalokować nową tablicę, a wywołujący nigdy nie zobaczy tego nowego nagłówka. Jeśli funkcja powiększa slice, zwróć nowy slice. Ta sama logika dotyczy map: funkcja może dodać klucze, które zobaczy wywołujący, bo wartość mapy odwołuje się do wspólnych danych.

Użyj parametru wskaźnikowego, gdy funkcja musi zmienić zmienną wywołującego albo gdy wartość to duża struktura, której nie chcesz kopiować przy każdym wywołaniu.

Funkcje są wartościami

Funkcja ma typ zapisywany bez nazwy: func(int, int) int. Funkcję możesz zapisać w zmiennej, umieścić w mapie lub slice'ie i przekazać do innej funkcji.

square to funkcja anonimowa przypisana do zmiennej. Funkcje anonimowe mogą też czytać i zmieniać zmienne z otaczającego zakresu, co czyni z nich domknięcia.

Nazwany typ funkcyjny ułatwia czytanie sygnatur, gdy ten sam kształt pojawia się w wielu miejscach:

type Transform func(int) int

func apply(xs []int, f Transform) []int

Wartość zerowa typu funkcyjnego to nil. Wywołanie funkcji nil kończy się panic z komunikatem invalid memory address or nil pointer dereference, więc sprawdzaj opcjonalne callbacki przed ich wywołaniem. Wartości funkcyjne można porównywać tylko z nil, nigdy ze sobą nawzajem.

Rekurencja

Funkcja może wywoływać samą siebie. Go nie ma optymalizacji wywołań ogonowych, ale stosy gorutyn rosną w miarę potrzeb, więc zwykłe głębokości rekurencji nie są problemem.

for i := range 6 liczy od 0 do 5 i wymaga Go 1.22 lub nowszego.

Czego nie mają funkcje w Go

  • Brak przeciążania. Dwie funkcje w jednym pakiecie nie mogą mieć tej samej nazwy, nawet z innymi parametrami.
  • Brak argumentów domyślnych i nazwanych. Każdy parametr jest przekazywany przy każdym wywołaniu, w kolejności. Struktura konfiguracji daje nazwane, opcjonalne pola: NewServer(Config{Port: 8080}).
  • Brak zagnieżdżonych funkcji nazwanych. W ciele funkcji możesz deklarować tylko funkcje anonimowe, zwykle przypisane do zmiennej.

Listy argumentów o zmiennej długości są obsługiwane przez funkcje wariadyczne.

Funkcje eksportowane i nieeksportowane

Funkcja, której nazwa zaczyna się wielką literą (Parse), jest eksportowana i można ją wywołać z innych pakietów. Nazwa zaczynająca się małą literą (parse) jest widoczna tylko w jej własnym pakiecie. Nie ma słów kluczowych public ani private; cała reguła to wielkość pierwszej litery.

Typowe błędy

  • Oczekiwanie, że zmiana parametru dotrze do wywołującego. Przypisanie do parametru zmienia tylko kopię. Zwróć nową wartość albo przyjmij wskaźnik.
  • Dopisywanie w funkcji i ignorowanie wyniku. func add(s []int) { s = append(s, 1) } nie wpływa na długość slice'a wywołującego. Napisz func add(s []int) []int i użyj s = add(s).
  • Nieużyte wyniki. Go pozwala zignorować wartość zwracaną. go vet nie zgłasza porzuconego błędu, ale lintery takie jak errcheck (część golangci-lint) tak. Używaj _ = tylko wtedy, gdy ignorowanie to świadoma decyzja.

Najczęściej zadawane pytania

Jak zdefiniować funkcję w Go?

Użyj słowa kluczowego func, nazwy, listy parametrów z typami po nazwach i typu wyniku: func add(a, b int) int { return a + b }. Funkcja bez wyniku pomija typ, a funkcja z kilkoma wynikami wymienia je w nawiasach: func divmod(a, b int) (int, int).

Czy Go przekazuje argumenty przez wartość, czy przez referencję?

Zawsze przez wartość. Funkcja dostaje kopię każdego argumentu. Dla int czy struktury ta kopia jest niezależna od zmiennej wywołującego. Slice'y, mapy, kanały i wskaźniki to małe wartości, które odwołują się do wspólnych danych, więc zmiany elementów, na które wskazują, są widoczne dla wywołującego, a przypisanie nowej wartości do samego parametru już nie. Przekaż wskaźnik (*T), gdy funkcja musi zmienić zmienną wywołującego.

Czy w Go można przekazać funkcję jako parametr?

Tak. Funkcje to wartości z typem, na przykład func(int) int. Zadeklaruj parametr tego typu i przekaż dowolną funkcję o pasującej sygnaturze, nazwaną lub anonimową: func apply(xs []int, f func(int) int).

Czy Go obsługuje przeciążanie funkcji lub parametry domyślne?

Nie. Każda nazwa funkcji w pakiecie musi być unikalna, a każdy parametr trzeba przekazać przy każdym wywołaniu. Typowe zamienniki to osobne nazwy (NewServer, NewServerWithTLS), struktura konfiguracji, parametry wariadyczne albo opcje funkcyjne.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ