x.(T): estrarre il valore da un'interfaccia
Un valore interfaccia nasconde il tipo concreto che contiene. Una type assertion x.(T) te lo restituisce.
Output:
gopher 6
0 false
string of length 6
x deve avere un tipo interfaccia. Un'asserzione su un tipo concreto non compila: invalid operation: s (variable of type string) is not an interface.
La forma a un valore va in panic
Quando l'asserzione è sbagliata, la forma a un solo valore va in panic con un messaggio che indica entrambi i tipi:
Output:
recovered: interface conversion: interface {} is int, not string
Usa la forma a un valore solo quando un tipo diverso sarebbe un errore di programmazione per cui vuoi che il programma si fermi. In tutti gli altri casi usa comma-ok. Se fallisce, comma-ok restituisce il valore zero di T e false.
Anche un'asserzione su un'interfaccia nil fallisce: var v any; v.(string) va in panic con interface conversion: interface {} is nil, not string, e la forma comma-ok restituisce "", false.
Asserzione verso un'altra interfaccia
T può essere un tipo interfaccia. L'asserzione riesce allora se il valore dinamico implementa T, e il risultato mantiene lo stesso valore dinamico. È così che la libreria standard verifica le funzionalità opzionali.
io.Copy verifica se la sua sorgente implementa io.WriterTo e, se sì, la usa. fmt verifica Stringer ed error. Questo schema permette a un'interfaccia piccola di restare piccola, mentre i chiamanti sfruttano comunque implementazioni più ricche.
Type switch
Quando un valore può essere di diversi tipi, un type switch sostituisce una catena di asserzioni. x.(type) è valido solo dentro uno switch.
Output:
hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int
Regole utili da conoscere:
- In un caso con un solo tipo,
xha quel tipo. In un caso che elenca più tipi, e indefault,xha il tipo dell'interfaccia originale (quiany). - I casi vengono controllati in ordine. Metti le interfacce più specifiche prima di quelle più ampie, perché un valore può soddisfarne diverse.
case nilcorrisponde solo a un'interfaccia nil, non a un'interfaccia che contiene un puntatore nil.- In un type switch non esiste
fallthrough.
errors.As: l'asserzione per gli errori incapsulati
Gli errori vengono spesso incapsulati aggiungendo contesto: fmt.Errorf("load config: %w", err). Una type assertion diretta guarda solo l'errore più esterno e non vede quello interno. errors.As percorre la catena.
Output:
type assertion finds it: false
errors.As finds it: open /no/such/file
Usa una type assertion sugli errori solo quando sai che l'errore non è incapsulato, cioè in pratica quasi mai. La pagina sugli errori personalizzati tratta errors.As, errors.Is e la definizione dei tuoi tipi di errore.
Asserzioni, conversioni e generics
| Hai | Vuoi | Usa |
|---|---|---|
int | float64 | conversione: float64(n) |
un any che contiene un int | l'int | asserzione: v.(int) |
un any di diversi tipi possibili | diramare in base al tipo | type switch |
un error che può essere incapsulato | un tipo di errore specifico | errors.As |
| una funzione che funziona per molti tipi | sicurezza in fase di compilazione | generics |
Un codice pieno di any e type switch è spesso il segnale che i generics o un'interfaccia ben fatta esprimerebbero meglio l'intenzione e troverebbero gli errori in fase di compilazione.
Errori comuni
- Usare la forma che va in panic su dati non affidabili. JSON decodificato, valori di mappa di tipo
any, input di plugin: sempre comma-ok. - Asserire il tipo numerico sbagliato. I numeri JSON vengono decodificati in
anycomefloat64, quindiv.(int)fallisce su di essi. - Asserire un tipo valore quando è memorizzato un puntatore. Se l'interfaccia contiene
*User, allorav.(User)fallisce. Asserisciv.(*User). - Type assertion sugli errori. Usa
errors.As.
Domande frequenti
Cos'è una type assertion in Go?
È un'espressione x.(T) dove x è un valore interfaccia. Se T è un tipo concreto, restituisce il valore contenuto in x come T. Se T è un tipo interfaccia, verifica che anche il valore contenuto implementi T. La forma a un valore va in panic quando la verifica fallisce; la forma a due valori v, ok := x.(T) lo segnala in ok.
Come verifico il tipo di un valore interfaccia in Go?
Usa un type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. Dentro ogni caso v ha il tipo di quel caso. Per un solo tipo, l'asserzione comma-ok s, ok := x.(string) è più breve. fmt.Printf("%T", x) stampa il nome del tipo per il debug.
Qual è la differenza tra type assertion e conversione di tipo in Go?
Una conversione T(x) trasforma un valore di un tipo in un altro, come float64(n); è il compilatore a decidere se è permessa, e non può fallire durante l'esecuzione. Un'asserzione x.(T) funziona solo sui valori interfaccia e verifica durante l'esecuzione quale tipo è contenuto. Non puoi scrivere int(x) per un any che contiene un int; ti serve x.(int).
Devo usare una type assertion per controllare i tipi di errore?
No. Usa errors.As(err, &target). Un'asserzione diretta err.(*MyError) guarda solo l'errore più esterno, quindi fallisce appena l'errore viene incapsulato con fmt.Errorf("...: %w", err). errors.As percorre la catena degli errori incapsulati.