x.(T): tirando o valor de dentro de uma interface
Um valor de interface esconde o tipo concreto que guarda. Uma type assertion x.(T) pede esse tipo de volta.
Saída:
gopher 6
0 false
string of length 6
x precisa ter um tipo interface. Fazer assertion sobre um tipo concreto não compila: invalid operation: s (variable of type string) is not an interface.
A forma de um valor causa panic
Quando a assertion está errada, a forma de um único valor causa panic com uma mensagem que nomeia os dois tipos:
Saída:
recovered: interface conversion: interface {} is int, not string
Use a forma de um valor só quando um tipo diferente for um erro de programação no qual você quer que o programa caia. Em todos os outros casos, use vírgula-ok. Em caso de falha, a vírgula-ok devolve o valor zero de T e false.
Fazer assertion em uma interface nil também falha: var v any; v.(string) causa panic com interface conversion: interface {} is nil, not string, e a forma vírgula-ok devolve "", false.
Assertion para outra interface
T pode ser um tipo interface. Nesse caso a assertion dá certo se o valor dinâmico implementar T, e o resultado mantém o mesmo valor dinâmico. É assim que a biblioteca padrão verifica capacidades opcionais.
O io.Copy verifica se a origem implementa io.WriterTo e o usa quando implementa. O fmt verifica Stringer e error. O padrão permite que uma interface pequena continue pequena enquanto quem chama ainda se beneficia de implementações mais ricas.
Type switch
Quando um valor pode ser de um entre vários tipos, um type switch substitui uma cadeia de assertions. x.(type) só é válido dentro de um switch.
Saída:
hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int
Regras que valem a pena conhecer:
- Em um case com um único tipo,
xtem esse tipo. Em um case que lista vários tipos, e nodefault,xtem o tipo da interface original (anyaqui). - Os cases são verificados em ordem. Coloque interfaces mais específicas antes das mais amplas, já que um valor pode satisfazer várias.
case nilsó casa com uma interface nil, não com uma interface que guarda um ponteiro nil.- Não existe
fallthroughem um type switch.
errors.As: a assertion para erros empacotados
Erros são muitas vezes empacotados com contexto: fmt.Errorf("load config: %w", err). Uma type assertion direta só olha o erro mais externo e deixa passar o que está dentro. O errors.As percorre a cadeia.
Saída:
type assertion finds it: false
errors.As finds it: open /no/such/file
Use type assertion em erros só quando você sabe que o erro não está empacotado, o que na prática quer dizer quase nunca. A página de erros personalizados cobre errors.As, errors.Is e a definição dos seus próprios tipos de erro.
Assertions, conversões e generics
| Você tem | Você quer | Use |
|---|---|---|
int | float64 | conversão: float64(n) |
any guardando um int | o int | assertion: v.(int) |
any com vários tipos possíveis | ramificar pelo tipo | type switch |
um error que pode estar empacotado | um tipo de erro específico | errors.As |
| uma função que serve para muitos tipos | segurança em tempo de compilação | generics |
Código cheio de any e type switches costuma ser sinal de que generics ou uma interface adequada expressariam melhor a intenção e pegariam erros em tempo de compilação.
Erros comuns
- Usar a forma que causa panic em dados não confiáveis. JSON decodificado, valores de map do tipo
any, entradas de plugins: sempre vírgula-ok. - Fazer assertion para o tipo numérico errado. Números JSON são decodificados em
anycomofloat64, entãov.(int)falha com eles. - Fazer assertion de um tipo valor quando um ponteiro está guardado. Se a interface guarda
*User, entãov.(User)falha. Façav.(*User). - Type assertions em erros. Use
errors.As.
Perguntas frequentes
O que é uma type assertion em Go?
Uma expressão x.(T) em que x é um valor de interface. Se T é um tipo concreto, ela devolve o valor guardado em x como um T. Se T é um tipo interface, ela verifica se o valor guardado também implementa T. A forma de um valor causa panic quando a verificação falha; a forma de dois valores v, ok := x.(T) informa isso em ok.
Como verificar o tipo de um valor de interface em Go?
Use um type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. Dentro de cada case, v tem o tipo daquele case. Para um único tipo, a assertion vírgula-ok s, ok := x.(string) é mais curta. fmt.Printf("%T", x) imprime o nome do tipo para depuração.
Qual a diferença entre type assertion e conversão de tipo em Go?
Uma conversão T(x) transforma um valor de um tipo em outro, como float64(n); o compilador decide se ela é permitida, e ela não pode falhar em tempo de execução. Uma assertion x.(T) só funciona em valores de interface e verifica em tempo de execução qual tipo está guardado dentro. Você não pode escrever int(x) para um any que guarda um int; precisa de x.(int).
Devo usar type assertion para verificar tipos de erro?
Não. Use errors.As(err, &target). Uma assertion direta err.(*MyError) só olha o erro mais externo, então falha assim que o erro é empacotado com fmt.Errorf("...: %w", err). O errors.As percorre a cadeia de empacotamento.