GoSuda

Any não é Any, mas Any é Any

By snowmerak
views ...

Any

Em Go, o tipo any é um alias para interface{}, uma interface mágica que pode conter qualquer tipo. Isso significa que qualquer tipo pode ser atribuído a uma variável do tipo any.

1var a any
2a = 42          // int
3a = "hello"     // string
4a = true        // bool
5a = 3.14        // float64

Dessa forma, o tipo any pode armazenar valores de diversos tipos, permitindo a escrita de código flexível. Contudo, ao utilizar o tipo any, há alguns pontos que exigem atenção.

Type Assertion

Para utilizar o valor real de uma variável do tipo any, é necessário empregar a asserção de tipo (type assertion). A asserção de tipo informa ao compilador que a variável do tipo any é de um tipo específico.

1var a any = "hello"
2str, ok := a.(string)
3if ok {
4    fmt.Println("String value:", str)
5} else {
6    fmt.Println("Not a string")
7}

No exemplo acima, a.(string) afirma que a é do tipo string. Se a não for do tipo string, ok será false, e o programa poderá evitar um pânico (panic).

Type Switch

O switch de tipo (type switch) permite executar diferentes ações dependendo do tipo real de uma variável any. Isso é útil quando é necessário processar múltiplos tipos.

 1var a any = 42
 2switch v := a.(type) {
 3case int:
 4    fmt.Println("Integer:", v)
 5case string:
 6    fmt.Println("String:", v)
 7case bool:
 8    fmt.Println("Boolean:", v)
 9default:
10    fmt.Println("Unknown type")
11}

No exemplo acima, a.(type) verifica o tipo real de a e executa o caso correspondente a esse tipo.

Reflection

O pacote reflect da linguagem Go permite inspecionar e manipular dinamicamente o tipo e o valor de variáveis any em tempo de execução. Isso é útil ao lidar com estruturas de dados complexas.

1import (
2    "fmt"
3    "reflect"
4)
5
6var a any = 3.14
7v := reflect.ValueOf(a)
8fmt.Println("Type:", v.Type())
9fmt.Println("Value:", v.Float())

No exemplo acima, utilizando o pacote reflect, é possível verificar o tipo e o valor de a em tempo de execução.

EFace

Na linguagem Go, as interfaces são divididas em duas formas principais: EFace (Empty Interface) e IFace (Interface with Methods). Dentre elas, a EFace é o mesmo conceito que o tipo any que abordamos até agora.

Estrutura

A EFace é uma estrutura especial que existe apenas no tempo de execução da linguagem Go, portanto, aqui faremos uma mímica para tratá-la.

 1type runtimeTypeMimic struct {
 2	Size_       uintptr
 3	PtrBytes    uintptr // number of (prefix) bytes in the type that can contain pointers
 4	Hash        uint32  // hash of type; avoids computation in hash tables
 5	TFlag       uint8   // extra type information flags
 6	Align_      uint8   // alignment of variable with this type
 7	FieldAlign_ uint8   // alignment of struct field with this type
 8	Kind_       uint8   // enumeration for C
 9	Equal       func(unsafe.Pointer, unsafe.Pointer) bool
10	GCData      *byte
11	Str         int32 // string form
12	PtrToThis   int32 // type for pointer to this type, may be zero
13}
14
15type eFaceMimic struct {
16	Type *runtimeTypeMimic
17	Data unsafe.Pointer
18}

A EFace possui dois campos:

  • Type: um ponteiro que contém metadados sobre o tipo real do valor.
  • Data: um ponteiro que contém o valor real.

E a EFace gerencia as informações de tipo internamente através da estrutura runtimeType. Essa estrutura inclui vários metadados, como o tamanho, alinhamento e valor de hash do tipo.

Dentro disso, os campos que devemos observar são Kind_, Hash e Equal. Kind_ representa o tipo da categoria, e Hash e Equal são usados para comparar tipos e calcular valores de hash em tabelas de hash.

Empacotamento

Agora, vamos encapsular esta EFace para que possamos realizar magia negra de forma mais conveniente.

 1type TypeInfo struct {
 2	Hash  uint32
 3	TFlag uint8
 4	Kind  uint8
 5	Equal func(unsafe.Pointer, unsafe.Pointer) bool
 6	This  unsafe.Pointer
 7}
 8
 9func GetTypeInfo(v any) TypeInfo {
10	eface := *(*eFaceMimic)(unsafe.Pointer(&v))
11	return TypeInfo{
12		Hash:  eface.Type.Hash,
13		TFlag: eface.Type.TFlag,
14		Kind:  eface.Type.Kind_,
15		Equal: eface.Type.Equal,
16		This:  eface.Data,
17	}
18}

A função acima recebe uma variável do tipo any, extrai a estrutura EFace interna e, a partir dela, recupera as informações de tipo, retornando-as em uma estrutura TypeInfo. Agora, utilizando esta TypeInfo, é possível acessar facilmente o valor de hash do tipo, a categoria, a função de comparação, entre outros.

Utilização

A utilização é realmente simples. Basta passar a variável do tipo any para a função GetTypeInfo.

 1func main() {
 2	i := 42
 3	eface := GetTypeInfo(i)
 4	fmt.Printf("%+v\n", eface)
 5
 6	f := 3.14
 7	eface2 := GetTypeInfo(f)
 8	fmt.Printf("%+v\n", eface2)
 9
10	// Compare the two values using the Equal function from the type info
11	log.Println(eface.Equal(eface.This, eface.This))
12	log.Println(eface2.Equal(eface2.This, eface2.This))
13	log.Println(eface2.Equal(eface.This, eface2.This))
14	log.Println(eface.Equal(eface2.This, eface.This))
15}

No exemplo acima, valores inteiros e de ponto flutuante são passados para a função GetTypeInfo para exibir suas respectivas informações de tipo, e a função Equal é utilizada para comparar os valores. Isso permite obter informações de tipo de forma mais rápida e comparar tipos.

Além disso, este método pode ser usado de forma idêntica para estruturas, ponteiros e interfaces, e também pode resolver o problema de "nil tipado" (typed nil), que ocorre com frequência. Embora o Hash seja alocado se houver um tipo, o This é 0 se for nil.

Conclusão

A EFace desempenha um papel crucial no tratamento do tipo any na linguagem Go atualmente. Através dela, é possível processar valores de diversos tipos de forma flexível e rápida, além de obter e utilizar informações detalhadas sobre os tipos. Contudo, é importante notar que esta é uma funcionalidade interna do tempo de execução e, portanto, pode ser afetada por alterações nas especificações da linguagem.