green tree

Qual a diferença entre reflect.TypeOf e reflect.ValueOf

Se você é novo no mundo Go, muito provavelmente ainda não tenha trabalhado diretamente com o package reflect. Na verdade, é completamente normal pessoas que já trabalhem com a linguagem a algum tempo e ainda não tenham utilizado tal package.

Isso por que esse package tem uma utilidade muito específica. Não que todos os packages não tenham, ou pelo menos deveriam ter. Mas no caso do reflect, sua especificidade é tão grande, que mesmo em sistemas que o utilizam, ele provavelmente fica em alguma parte obscura, complexa e pouco mantida.

Tendo dito tudo isso e, ao mesmo tempo falado muito pouco, nesse post explicarei tudo o que você precisa saber sobre o package reflect e suas funções TypeOf e ValueOf.

Reflect

Antes de entender o que as funções TypeOf e ValueOf tem a oferecer, falemos um pouco sobre a finalidade do package reflect.

Esse package, em linhas gerais, foi criado para que nós desenvolvedores possamos examinar, criar e modificar tipos, variáveis, funções e structs em tempo de execução.

Nesse momento você deve estar pensando… “Mas Tiago, se o Go é uma linguagem de tipagem forte, como eu posso receber um valor sem saber o seu tipo?”

Simples. Usarei como exemplo uma aplicação que fiz para testar a resposta de endpoints. Como toda a parte de configuração de payload e response é genérica e feita por arquivos YAML, sempre que verifico se a response do endpoint é a esperada, preciso, em runtime, descobrir qual tipo de dado e valor foi retornado.

Nesse momento, entra em cena a primeira função que exploraremos, o TypeOf.

reflect.TypeOf

Quando a função reflect.TypeOf(VAR) é executada, uma struct do tipo reflect.Type é devolvida. Com essa struct, podemos descobrir algumas informações sobre a variável que está sendo analisada, através dos métodos disponíveis na struct reflect.Type.

Por exemplo, o método Name(), que retorna o nome do tipo. Ou o método Kind(), que devolve do que o tipo é feito — slice, map, pointer, struct, interface, string, array, function, int, etc.

Se o parágrafo acima não ficou confuso, execute o código abaixo. Se ficou, execute também. Talvez piore a confusão.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var t any = "OK"

	rt := reflect.TypeOf(t)

	fmt.Println(rt.Name()) // output: string
	fmt.Println(rt.Kind()) // output: string
}

Pois é! Embora o output nesse caso seja igual, existe uma grande diferença entre esses dois métodos. Para ilustrar melhor, imagine que temos uma struct com nome de Person. Se o reflect.TypeOf for executado em uma variável que contém essa struct, o método Name() iria retornar Person. Já o método Kind() retornaria o valor struct.

Para saber o tipo contido em uma variável do tipo ponteiro, map, slice, channel ou array, precisamos utilizar o método Elem().

Caso o tipo analisado seja uma struct, métodos como NumField(), que retorna a quantidade de campos em uma struct, e Field(INDEX), que retorna detalhes do campo, ajudarão muito na hora de entender a estrutura da struct ou recuperar valores de tags. Se quiser saber mais sobre tags, confira os posts:

reflect.ValueOf

Com essa função, além de conseguir obter informações do tipo com o método Type() — que retorna uma struct do tipo reflect.Type —, também conseguimos ler, modificar ou criar novos valores.

No exemplo abaixo nós vamos:

  1. Validar o tipo da variável;
  2. Ler o valor contido na variável;
  3. Por fim, modificar seu valor.
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var t any = "OK" // definimos uma interface any e setamos o valor OK

	v := reflect.ValueOf(&t) // obtemos o reflect.Value
	if v.Elem().Kind() == reflect.Interface { // validamos se o tipo dela é interface
		fmt.Println(v.Elem().Interface()) // lemos a variável
		v.Elem().Set(reflect.ValueOf("NOT OK")) // modificamos a variável
	}

	fmt.Println(t) // printamos o novo valor
}

⚠️ NOTA! Para ser possível fazer a modificação do valor da variável, ela precisa ser passada como ponteiro para a função ValueOf.

Conclusão

O package reflect adiciona uma enorme gama de possibilidades. No entanto, existe um trade-off chamado desempenho. Por isso, não abuse do seu uso.

Caso queiram ver mais posts sobre esse package, deixe um comentário.

Obrigado e até a próxima!


Processing…
Success! You're on the list.

Deixe uma resposta