Menu

Zwracanie wielu wartości w Go: (value, error), nazwane wyniki

Funkcje w Go mogą zwracać kilka wartości. Ta strona omawia składnię, konwencję (value, error), idiom comma-ok, nazwane wyniki, gołe return i pusty identyfikator.

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

Zwracanie więcej niż jednej wartości

Wymień typy wyników w nawiasach. Zwracaj wartości oddzielone przecinkami i odbieraj je w ten sam sposób.

Wbudowane min i max istnieją od Go 1.21. Kod wywołujący musi odebrać każdy wynik: x := minMax(nums) się nie kompiluje i zgłasza assignment mismatch: 1 variable but minMax returns 2 values.

Konwencja (value, error)

Najczęstszy powód zwracania dwóch wartości to zgłaszanie niepowodzenia. Zgodnie z konwencją błąd jest ostatni, a gdy nie jest nil, pozostałe wyniki należy traktować jako pozbawione znaczenia.

Wynik:

port: 8080
error: port "http": strconv.Atoi: parsing "http": invalid syntax
error: port out of range

Przy niepowodzeniu zwracaj wartość zerową dla każdego innego wyniku (0, "", nil). Pełny wzorzec, łącznie z opakowywaniem i sprawdzaniem, jest na stronie o obsłudze błędów.

Idiom comma-ok

Kilka wbudowanych operacji zwraca opcjonalny drugi bool, który mówi, czy pierwsza wartość jest prawdziwa. Zmienną zwyczajowo nazywa się ok.

Te trzy formy (odczyt z mapy, asercja typu, odbiór z kanału) są wyjątkowe: zwracają jedną lub dwie wartości w zależności od tego, do ilu zmiennych je przypisujesz. Twoje własne funkcje tak nie potrafią. Funkcja zadeklarowana z dwoma wynikami zawsze zwraca dwa.

Pusty identyfikator

_ odbiera wartość i ją wyrzuca. Używaj go dla wyników, których nie potrzebujesz:

_, err := fmt.Sscan(input, &n)
key, _ := parseLine(line)

Zignorowanie błędu przez _ się kompiluje, więc rób to tylko wtedy, gdy wiesz, że wywołanie nie może zawieść albo niepowodzenie nie ma znaczenia. Na przykład strings.Builder.WriteString zawsze zwraca błąd nil; zamknięcie pliku po zapisie już nie.

Nazwane wyniki

Parametry wynikowe mogą mieć nazwy. Są deklarowane na początku funkcji, inicjalizowane wartościami zerowymi, a return bez wartości zwraca to, co akurat w nich jest.

Nazwy pojawiają się w wyniku go doc, co pomaga, gdy dwa wyniki mają ten sam typ: (quotient, remainder int) mówi więcej niż (int, int).

Samo return w długiej funkcji trudno się czyta, bo trzeba cofać się wzrokiem, żeby zobaczyć, co zawierają wyniki. Typowa rada: nazywaj wyniki, gdy nazwy coś dokumentują, a mimo to pisz jawnie return quotient, remainder, chyba że funkcja ma kilka linii.

Nazwane wyniki i defer

Funkcja odroczona wykonuje się po tym, jak instrukcja return ustawi wyniki, a zanim otrzyma je kod wywołujący. Przy nazwanych wynikach funkcja odroczona może je odczytać i zmienić. W ten sposób dodaje się kontekst do każdego błędu zwracanego przez funkcję albo zamienia panikę w błąd.

To wypisuje load : empty name, a potem <nil>. return errors.New(...) najpierw przypisuje wartość do err, a potem funkcja odroczona ją opakowuje. Więcej w artykule o defer.

Przesłanianie nazwanego wyniku

Nazwane wyniki żyją w najbardziej zewnętrznym zasięgu funkcji. := wewnątrz zagnieżdżonego bloku tworzy nową zmienną o tej samej nazwie, a samo return zwraca wtedy tę zewnętrzną.

func find() (n int, err error) {
	if true {
		n, err := compute() // new n and err, shadowing the results
		_ = n
		_ = err
	}
	return // returns 0, nil
}

Kompilator wyłapuje najgorszy przypadek: samo return wewnątrz bloku, w którym wynik jest przesłonięty, kończy się błędem result parameter err not in scope at return. Nie wyłapuje wersji powyżej, gdzie return stoi poza blokiem. Używaj = zamiast :=, gdy chodzi ci o zmienne wynikowe.

Częste błędy

  • Używanie wartości, gdy err nie jest nil. Najpierw sprawdź err. Funkcja może razem z błędem zwrócić cokolwiek.
  • Zwracanie typowanego wskaźnika nil jako błędu. var e *MyErr; return e zwraca error, który nie jest nil. Przy sukcesie zwracaj dosłowne nil.
  • Zbyt wiele wyników. Trzy lub więcej wyników o różnym znaczeniu trudno używać. Zwróć zamiast tego strukturę.

Najczęściej zadawane pytania

Jak zwrócić wiele wartości z funkcji w Go?

Wymień typy wyników w nawiasach i zwróć wartości oddzielone przecinkami: func minMax(xs []int) (int, int) { ...; return lo, hi }. Kod wywołujący przypisuje je wszystkie naraz: lo, hi := minMax(nums).

Jak w Go zignorować jedną z wartości zwracanych?

Przypisz ją do pustego identyfikatora _: _, err := strconv.Atoi(s) albo n, _ := strconv.Atoi(s). Nie można całkiem pominąć wartości: liczba zmiennych po lewej stronie musi się zgadzać z liczbą wyników.

Czym są nazwane wartości zwracane w Go?

To parametry wynikowe z nazwami, np. func split(n int) (half, rest int). Startują z wartościami zerowymi, można je przypisywać w ciele funkcji, a samo return zwraca ich bieżące wartości. Pozwalają też funkcji odroczonej zmienić to, co dostanie kod wywołujący.

Czy w Go używać gołego return?

Tylko w krótkich funkcjach. Samo return w długiej funkcji ukrywa, co jest zwracane, i ułatwia błędy z przesłanianiem. Przewodniki stylu Go zalecają nazywanie wyników dla dokumentacji i jawne zwracanie wartości we wszystkim, co ma więcej niż kilka linii.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ