Go nie ma operatora trójargumentowego. Nie ma cond ? a : b. Idiomatycznym zamiennikiem jest instrukcja if:
Zadeklaruj zmienną z wartością domyślną, a potem zmień ją w if. Gdy obie gałęzie wymagają pracy, użyj if/else:
var fee int
if member {
fee = 0
} else {
fee = computeFee(order)
}
Dlaczego Go go pominęło
Go FAQ odpowiada na to wprost: projektanci widzieli, jak operator ?: zbyt często służył do tworzenia nieprzeniknienie złożonych wyrażeń, i uznali, że if/else, choć dłuższe, jest bez wątpienia czytelniejsze. Język potrzebuje tylko jednej konstrukcji warunkowego sterowania przepływem.
W praktyce brak tego operatora kosztuje dwie lub trzy linie przy każdym użyciu i usuwa całą kategorię zagnieżdżonych wyrażeń w stylu a ? b ? c : d : e.
Wczesny return zamiast tego
Wewnątrz funkcji najczystsza forma często w ogóle pomija zmienną:
Wyciągnięcie warunku do małej nazwanej funkcji to często najlepsze rozwiązanie: miejsce wywołania czyta się jak upragniony operator trójargumentowy, a nazwa funkcji dokumentuje, co oznacza warunek.
Wartości domyślne z cmp.Or (Go 1.22)
Najczęstszy operator trójargumentowy w innych językach to „użyj tej wartości albo zapasowej, jeśli jest pusta”: name ? name : "anonymous" lub name || "anonymous" w JavaScripcie. Go 1.22 dodało dokładnie do tego cmp.Or. Zwraca pierwszy argument, który nie jest wartością zerową swojego typu:
Traktuje wartość zerową jako „brak”, więc nie odróżni sytuacji „port celowo ma 0” od „portu nie ustawiono”. Gdy zero jest poprawną wartością, użyj wskaźnika, osobnego bool albo jawnego if.
Generyczna funkcja If i jej pułapka
Dzięki generykom (Go 1.18) możesz napisać funkcję, która wygląda jak operator trójargumentowy:
func If[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}
status := If(ok, "pass", "fail")
Działa dla prostych wartości i niektóre projekty jej używają. Ale to nie jest operator trójargumentowy, a różnica ma znaczenie. Go oblicza wszystkie argumenty przed wywołaniem funkcji, więc zarówno a, jak i b są zawsze liczone:
Pierwsze wywołanie oblicza zarówno "a", jak i "b", a drugie kończy się panic z dereferencją wskaźnika nil, mimo że warunek jest fałszywy. Prawdziwy operator trójargumentowy obliczyłby tylko wybraną stronę. Dlatego:
- Nigdy nie używaj takiej funkcji, gdy jedna strona wykonuje dereferencję wskaźnika, indeksuje slice albo czyta element mapy, który może nie istnieć.
- Nigdy nie używaj jej, gdy jedna strona jest kosztowna lub ma efekty uboczne.
- Dla tanich, bezpiecznych wartości działa, ale wielu recenzentów kodu Go i tak poprosi o zwykłe
if.
Możesz uczynić gałęzie leniwymi, przekazując funkcje, If(ok, func() string { return a }, func() string { return b }), ale wtedy instrukcja if jest krótsza.
Inne sztuczki i dlaczego ich unikać
Wyszukiwanie w mapie. map[bool]string{true: "yes", false: "no"}[ok] działa, ale przy każdym obliczeniu buduje mapę i haszuje klucz, a do tego oblicza obie wartości. To ciekawostka, a nie idiom.
Funkcja wywołana od razu. Anonimowa funkcja wywołana w miejscu daje wyrażenie:
label := func() string {
if n > 0 {
return "positive"
}
return "non-positive"
}()
To rzeczywiście oblicza się leniwie i czasem pomaga wewnątrz literału złożonego, gdzie nie można umieścić instrukcji. Przeważnie czytelniej jest obliczyć wartość w zmiennej tuż przed literałem.
Wybór zamiennika
| Sytuacja | Użyj |
|---|---|
| Wybór między dwiema wartościami | wartość domyślna, potem if |
| Obie gałęzie wymagają obliczeń | if/else |
| Wartość decyduje o wyniku funkcji | wczesny return |
| Wartość zapasowa, gdy wartość jest pusta | cmp.Or(value, fallback) |
| Trzy lub więcej przypadków | switch |
| Powtarzany warunek z konkretnym znaczeniem | mała nazwana funkcja |
Propozycje dodania wyrażenia warunkowego regularnie pojawiają się w issue trackerze Go i jak dotąd były odrzucane. Napisz if.
Najczęściej zadawane pytania
Czy Go ma operator trójargumentowy?
Nie. Go nie ma wyrażenia cond ? a : b. Według Go FAQ projektanci pominęli go, bo zbyt często służy do budowania wyrażeń trudnych do przeczytania. Użyj zamiast tego instrukcji if/else.
Jak napisać przypisanie warunkowe w jednej linii w Go?
Najpierw przypisz wartość domyślną, a potem ją nadpisz:
label := "odd"
if n%2 == 0 {
label = "even"
}
To idiomatyczny zamiennik. gofmt nie umieści ciała if w jednej linii, więc zawsze zajmuje to co najmniej trzy linie.
Czy mogę napisać generyczną funkcję trójargumentową w Go?
Tak: func If[T any](cond bool, a, b T) T { if cond { return a }; return b }. Haczyk polega na tym, że Go oblicza każdy argument przed wywołaniem funkcji, więc liczone są zarówno a, jak i b. If(p != nil, p.Name, "") nadal wykonuje dereferencję p równego nil i kończy się panic. Prawdziwy operator trójargumentowy obliczyłby tylko jedną stronę.
Jak ustawić wartość domyślną, gdy string jest pusty w Go?
Od Go 1.22 cmp.Or(name, "anonymous") zwraca pierwszy argument, który nie jest wartością zerową, więc daje name, chyba że name to "". Działa dla każdego porównywalnego typu i jak każde wywołanie funkcji oblicza wszystkie swoje argumenty.