Interface vazia
Veja o vídeo desse arquivo aqui.
Interface vazia é um tipo que aceita qualquer coisa, você pode passar o que quiser como parâmetros de função ou variáveis do tipo interface{}
.
Parece muito pratico a primeira vista mas quando usamos interface{}
estamos jogando pela janela a validação de tipos feita em tempo de compilação e perdemos uma das grandes vantagens de uma linguagem compilada de tipagem forte e estática.
E como a checagem de tipo não vai acontecer em tempo de compilação é sua responsabilidade checar se esta recebendo o tipo certo em tempo de execução.
Identificando o tipo
Quando queremos saber o tipo de um determinado principalmente quando estamos depurando e tentando ver se o tipo que chegou foi o topo esperado podemos usar a função fmt.Printf
passando o formato %T
como no exemplo.
var value interface{}
value = 1
fmt.Printf("tipo de value: %T\n", value)
No nosso exemplo declaramos a variável value
como interface{}
e agora podemos passar qualquer valor para ela, como passamos um inteiro a função Printf
com o formato %T
vai informar que a variável é do tipo int
.
Vamos ver um exemplo completo
package main
import (
"fmt"
)
func main() {
var value interface{}
value = 1
fmt.Printf("tipo de value: %T\n", value)
value = 3.14
fmt.Printf("tipo de value: %T\n", value)
value = "isso é uma string"
fmt.Printf("tipo de value: %T\n", value)
}
Esse pequeno programa deve mostrar o seguinte resultado
tipo de value: int
tipo de value: float64
tipo de value: string
Switch
Inspecionar o tipo de uma variável como vimos é bem simples, ajuda bastante a depurar o código e saber se estamos recebendo o que esperamos, agora vamos fazer nosso código fazer esse trabalho sozinho.
Vamos usar a combinação do switch com o cast (type)
que vai retornar o tipo da variável, veja o exemplo.
package main
import (
"fmt"
)
func main() {
var value interface{}
value = 1
switch value.(type) {
case int:
fmt.Println("Value é do tipo int")
case string:
fmt.Println("Value é do tipo string")
default:
fmt.Printf("Tipo %T não implementado\n", value)
}
}
Se a variável value
conter um int
nosso programa vai retornar Value é do tipo int
, se passarmos uma string para value
o retorno vai ser Value é do tipo string
e se passarmos qualquer outro valor o programa vai avisar que o suporte para aquele tipo ainda não esta implementado.
Uma vez que já sabemos o tipo da variável podemos fazer cast para o tipo correto e dai e dai usar sem problemas.
switch value.(type) {
case int:
fmt.Println(value.(int))
case string:
fmt.Println(value.(string))
Se não fizermos essa validação e simplesmente usar o cast para tentar converter a variável para o tipo que queremos corremos o risco do programa quebrar com um panic
caso o tipo passado não seja o que estamos esperando.
map[string]interface
Um uso muito comum é usar interfaces vazias em conjunto com mapas para converter strings JSON para estruturas que não conhecemos com certeza o formato. Como não sabemos quais campos e nem os tipos dos campos a estrutura contem podemos usar um map[string]interface{}
que basicamente casa com qualquer estrutura.
package main
import (
"fmt"
"encoding/json"
)
func main() {
b := []byte(`{"Name":"Cesar","Value":10}`)
var m map[string]interface{}
m = make(map[string]interface{})
err := json.Unmarshal(b, &m)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", m)
}
Neste exemplo primeiro declaramos um array de bytes com uma estrutura JSON, depois declaramos um mapa de strings e interfaces vazias, e instanciamos na memória. Daí usamos json.Unmarshal
para ler os array de bytes e popular o mapa com os campos e valores encontrados. Verificamos se houve algum erro porque sempre tem a possibilidade do JSON ser inválido e então usamos mais um pequeno truque para mostrar a estrutura. O formato %#v
na função fmt.Printf
para mostrar mais dados e não apenas o valor como aconteceria se usássemos apenas o formato %v
.
Vamos ver um exemplo mais completo usando range
para percorrer os campos do JSON e switch
para desviar o código para o tipo correto.
package main
import (
"encoding/json"
"fmt"
)
func main() {
b := []byte(`{"Name":"Banana","Value":2.10}`)
var m map[string]interface{}
m = make(map[string]interface{})
err := json.Unmarshal(b, &m)
if err != nil {
fmt.Println(err)
return
}
for k, v := range m {
switch v.(type) {
case float64:
fmt.Printf("%v %v\n", k, v.(float64))
case string:
fmt.Printf("%v %v\n", k, v.(string))
default:
fmt.Printf("Tipo %T não implementado\n", v)
}
}
}
Mais uma forma de como validar se a interface vazia tem o tipo que você quer é fazendo como no exemplo abaixo.
package main
import "fmt"
func main() {
var value interface{}
value = 1
str, ok := value.(string)
if !ok {
fmt.Println("Value não é string")
}
fmt.Println(str)
}
Aqui usamos str, ok := value.(string)
de forma que se value
for do tipo string
ok será true e str contera o valor propriamente dito já como string, é uma forma simples de testar o tipo sem escrever o switch case
.
Espero ter esclarecido mais um pouco sobre interface vazia, seus usos e porque pode ser arriscado.
Links úteis
- Código fonte dos exemplos de hoje
- Repositório do nosso grupo