x.(T): wydobywanie wartości z interfejsu
Wartość interfejsu ukrywa konkretny typ, który przechowuje. Asercja typu x.(T) prosi o jego zwrot.
Wynik:
gopher 6
0 false
string of length 6
x musi mieć typ interfejsu. Asercja na typie konkretnym się nie kompiluje: invalid operation: s (variable of type string) is not an interface.
Forma z jedną wartością wywołuje panic
Gdy asercja jest błędna, forma z jedną wartością wywołuje panic z komunikatem, który podaje oba typy:
Wynik:
recovered: interface conversion: interface {} is int, not string
Używaj formy z jedną wartością tylko wtedy, gdy inny typ byłby błędem programistycznym, przy którym program ma się wysypać. Wszędzie indziej używaj comma-ok. Przy niepowodzeniu comma-ok daje wartość zerową T i false.
Asercja na interfejsie nil też się nie udaje: var v any; v.(string) wywołuje panic z komunikatem interface conversion: interface {} is nil, not string, a forma comma-ok zwraca "", false.
Asercja do innego interfejsu
T może być typem interfejsu. Asercja udaje się wtedy, jeśli dynamiczna wartość implementuje T, a wynik zachowuje tę samą dynamiczną wartość. Tak biblioteka standardowa sprawdza opcjonalne możliwości.
io.Copy sprawdza, czy jego źródło implementuje io.WriterTo, i jeśli tak, używa go. fmt sprawdza Stringer i error. Ten wzorzec pozwala małemu interfejsowi pozostać małym, a wywołujący i tak korzystają z bogatszych implementacji.
Type switch
Gdy wartość może być jednym z kilku typów, type switch zastępuje łańcuch asercji. x.(type) jest poprawne tylko wewnątrz switch.
Wynik:
hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int
Zasady, które warto znać:
- W przypadku z jednym typem
xma ten typ. W przypadku z kilkoma typami oraz wdefaultxma typ oryginalnego interfejsu (tutajany). - Przypadki są sprawdzane po kolei. Bardziej szczegółowe interfejsy umieszczaj przed ogólniejszymi, bo wartość może spełniać kilka z nich.
case nilpasuje tylko do interfejsu nil, a nie do interfejsu przechowującego wskaźnik nil.- W type switch nie ma
fallthrough.
errors.As: asercja dla opakowanych błędów
Błędy często opakowuje się kontekstem: fmt.Errorf("load config: %w", err). Bezpośrednia asercja typu patrzy tylko na najbardziej zewnętrzny błąd i pomija ten w środku. errors.As przechodzi przez łańcuch.
Wynik:
type assertion finds it: false
errors.As finds it: open /no/such/file
Asercji typu na błędach używaj tylko wtedy, gdy wiesz, że błąd nie jest opakowany, czyli w praktyce prawie nigdy. Strona o własnych błędach omawia errors.As, errors.Is i definiowanie własnych typów błędów.
Asercje a konwersje a generyki
| Masz | Chcesz | Użyj |
|---|---|---|
int | float64 | konwersji: float64(n) |
any przechowujące int | ten int | asercji: v.(int) |
any jednego z kilku możliwych typów | rozgałęzienia według typu | type switch |
error, który może być opakowany | konkretnego typu błędu | errors.As |
| funkcję działającą dla wielu typów | bezpieczeństwa w czasie kompilacji | generyków |
Kod pełen any i type switch często oznacza, że generyki albo porządny interfejs lepiej wyraziłyby intencję i wyłapały błędy podczas kompilacji.
Typowe błędy
- Używanie formy z panic na niezaufanych danych. Zdekodowany JSON, wartości mapy typu
any, dane z wtyczek: zawsze comma-ok. - Asercja do złego typu liczbowego. Liczby z JSON dekodują się do
anyjakofloat64, więcv.(int)na nich zawodzi. - Asercja do typu wartości, gdy zapisany jest wskaźnik. Jeśli interfejs przechowuje
*User, tov.(User)zawodzi. Zrób asercjęv.(*User). - Asercje typu na błędach. Użyj
errors.As.
Najczęściej zadawane pytania
Czym jest asercja typu w Go?
To wyrażenie x.(T), w którym x jest wartością interfejsu. Jeśli T jest typem konkretnym, zwraca wartość zapisaną w x jako T. Jeśli T jest typem interfejsu, sprawdza, czy zapisana wartość też implementuje T. Forma z jedną wartością wywołuje panic, gdy sprawdzenie się nie powiedzie; forma z dwiema wartościami v, ok := x.(T) zgłasza to w ok.
Jak sprawdzić typ wartości interfejsu w Go?
Użyj type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. W każdym przypadku v ma typ tego przypadku. Dla jednego typu krótsza jest asercja comma-ok s, ok := x.(string). fmt.Printf("%T", x) wypisuje nazwę typu przy debugowaniu.
Czym różni się asercja typu od konwersji typu w Go?
Konwersja T(x) zamienia wartość jednego typu na inny, na przykład float64(n); kompilator decyduje, czy jest dozwolona, i nie może się ona nie powieść w czasie działania. Asercja x.(T) działa tylko na wartościach interfejsu i w czasie działania sprawdza, jaki typ jest w środku. Nie możesz napisać int(x) dla any przechowującego int; potrzebujesz x.(int).
Czy do sprawdzania typów błędów używać asercji typu?
Nie. Użyj errors.As(err, &target). Bezpośrednia asercja err.(*MyError) patrzy tylko na zewnętrzny błąd, więc zawodzi, gdy tylko błąd zostanie opakowany przez fmt.Errorf("...: %w", err). errors.As przechodzi przez cały łańcuch opakowań.