Menu

Aserción de tipo en Golang: x.(T), comma-ok y type switch

Una aserción de tipo saca el valor concreto de una interfaz. Aprende x.(T), la forma comma-ok que nunca provoca un panic, la aserción a otra interfaz, los type switches y errors.As para los errores envueltos.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

x.(T): sacar el valor de una interfaz

Un valor de interfaz oculta el tipo concreto que contiene. Una aserción de tipo x.(T) lo recupera.

Salida:

gopher 6
0 false
string of length 6

x tiene que tener un tipo interfaz. Hacer una aserción sobre un tipo concreto no compila: invalid operation: s (variable of type string) is not an interface.

La forma de un solo valor provoca un panic

Cuando la aserción es incorrecta, la forma de un solo valor provoca un panic con un mensaje que nombra los dos tipos:

Salida:

recovered: interface conversion: interface {} is int, not string

Usa la forma de un solo valor solo cuando otro tipo sería un error de programación ante el que quieres que el programa caiga. En todos los demás casos, usa comma-ok. Si falla, comma-ok da el valor cero de T y false.

Una aserción sobre una interfaz nil también falla: var v any; v.(string) provoca un panic con interface conversion: interface {} is nil, not string, y la forma comma-ok devuelve "", false.

Aserción a otra interfaz

T puede ser un tipo interfaz. La aserción tiene éxito entonces si el valor dinámico implementa T, y el resultado conserva el mismo valor dinámico. Así es como la librería estándar comprueba capacidades opcionales.

io.Copy comprueba si su origen implementa io.WriterTo y lo usa cuando lo hace. fmt comprueba Stringer y error. El patrón permite que una interfaz pequeña siga siendo pequeña mientras quien la usa aprovecha implementaciones más ricas.

Type switch

Cuando un valor puede ser de varios tipos, un type switch sustituye a una cadena de aserciones. x.(type) solo es válido dentro de un switch.

Salida:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

Reglas que conviene conocer:

  • En un caso con un solo tipo, x tiene ese tipo. En un caso que enumera varios tipos, y en default, x tiene el tipo de la interfaz original (aquí any).
  • Los casos se comprueban en orden. Pon las interfaces más específicas antes que las más amplias, porque un valor puede cumplir varias.
  • case nil solo coincide con una interfaz nil, no con una interfaz que contiene un puntero nil.
  • En un type switch no existe fallthrough.

errors.As: la aserción para errores envueltos

Los errores se suelen envolver con contexto: fmt.Errorf("load config: %w", err). Una aserción de tipo directa solo mira el error más exterior y no ve el de dentro. errors.As recorre la cadena.

Salida:

type assertion finds it: false
errors.As finds it: open /no/such/file

Usa una aserción de tipo sobre errores solo cuando sepas que el error no está envuelto, lo que en la práctica es casi nunca. La página de errores personalizados cubre errors.As, errors.Is y cómo definir tus propios tipos de error.

Aserciones frente a conversiones y genéricos

TienesQuieresUsa
intfloat64conversión: float64(n)
any que contiene un intel intaserción: v.(int)
any de varios tipos posiblesramificar según el tipotype switch
un error que puede estar envueltoun tipo de error concretoerrors.As
una función que sirva para muchos tiposseguridad en tiempo de compilacióngenéricos

Un código lleno de any y type switches suele indicar que los genéricos o una interfaz bien pensada expresarían mejor la intención y detectarían los errores al compilar.

Errores comunes

  • Usar la forma que provoca un panic con datos no fiables. JSON decodificado, valores de un map de tipo any, entradas de plugins: siempre comma-ok.
  • Hacer la aserción al tipo numérico equivocado. Los números JSON se decodifican en any como float64, así que v.(int) falla con ellos.
  • Hacer la aserción a un tipo valor cuando lo guardado es un puntero. Si la interfaz contiene *User, v.(User) falla. Haz la aserción v.(*User).
  • Aserciones de tipo sobre errores. Usa errors.As.

Preguntas frecuentes

¿Qué es una aserción de tipo en Go?

Una expresión x.(T) donde x es un valor de interfaz. Si T es un tipo concreto, devuelve el valor guardado en x como un T. Si T es un tipo interfaz, comprueba que el valor guardado también implementa T. La forma de un solo valor provoca un panic cuando la comprobación falla; la forma de dos valores v, ok := x.(T) lo informa en ok.

¿Cómo compruebo el tipo de un valor de interfaz en Go?

Usa un type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. Dentro de cada caso, v tiene el tipo de ese caso. Para un solo tipo, la aserción comma-ok s, ok := x.(string) es más corta. fmt.Printf("%T", x) imprime el nombre del tipo para depurar.

¿Qué diferencia hay entre una aserción de tipo y una conversión de tipo en Go?

Una conversión T(x) transforma un valor de un tipo en otro, como float64(n); el compilador decide si está permitida, y no puede fallar en tiempo de ejecución. Una aserción x.(T) solo funciona con valores de interfaz y comprueba en tiempo de ejecución qué tipo hay guardado dentro. No puedes escribir int(x) para un any que contiene un int; necesitas x.(int).

¿Debo usar una aserción de tipo para comprobar tipos de error?

No. Usa errors.As(err, &target). Una aserción directa err.(*MyError) solo mira el error exterior, así que falla en cuanto el error se envuelve con fmt.Errorf("...: %w", err). errors.As recorre la cadena de envoltorios.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR