Menu

Asercja typu w Golang: x.(T), comma-ok i type switch

Asercja typu wydobywa konkretną wartość z interfejsu. Poznaj x.(T), formę comma-ok, która nigdy nie wywołuje panic, asercję do innego interfejsu, type switch i errors.As dla opakowanych błędów.

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

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 x ma ten typ. W przypadku z kilkoma typami oraz w default x ma typ oryginalnego interfejsu (tutaj any).
  • Przypadki są sprawdzane po kolei. Bardziej szczegółowe interfejsy umieszczaj przed ogólniejszymi, bo wartość może spełniać kilka z nich.
  • case nil pasuje 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

MaszChceszUżyj
intfloat64konwersji: float64(n)
any przechowujące intten intasercji: v.(int)
any jednego z kilku możliwych typówrozgałęzienia według typutype switch
error, który może być opakowanykonkretnego typu błęduerrors.As
funkcję działającą dla wielu typówbezpieczeństwa w czasie kompilacjigenerykó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 any jako float64, więc v.(int) na nich zawodzi.
  • Asercja do typu wartości, gdy zapisany jest wskaźnik. Jeśli interfejs przechowuje *User, to v.(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ń.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ