GoSuda

„Any” nu este „Any”, dar „Any” este „Any”

By snowmerak
views ...

Any

În limbajul Go, tipul any este un alias pentru interfața magică interface{}, care poate conține orice tip. Astfel, orice tip poate fi atribuit unei variabile de tip any.

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

După cum se observă, tipul any poate conține valori de diverse tipuri, permițând scrierea unui cod flexibil. Totuși, există câteva aspecte de care trebuie să se țină cont la utilizarea tipului any.

Type Assertion

Pentru a utiliza valoarea reală dintr-o variabilă de tip any, este necesară utilizarea unei aserțiuni de tip (type assertion). Aserțiunea de tip informează compilatorul că variabila de tip any este de un anumit tip.

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}

În exemplul de mai sus, a.(string) asertează că a este de tip string. Dacă a nu este de tip string, ok va fi false, prevenind astfel o eroare de tip panic.

Type Switch

Un comutator de tip (type switch) permite executarea unor acțiuni diferite în funcție de tipul real al unei variabile de tip any. Acest lucru este util atunci când trebuie gestionate mai multe tipuri.

 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}

În exemplul de mai sus, a.(type) verifică tipul real al lui a și execută cazul corespunzător acelui tip.

Reflection

Pachetul reflect din limbajul Go permite inspectarea și manipularea dinamică a tipului și a valorii unei variabile de tip any la runtime. Acest lucru este util atunci când se lucrează cu structuri de date complexe.

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())

În exemplul de mai sus, pachetul reflect este utilizat pentru a verifica tipul și valoarea lui a la runtime.

EFace

În limbajul Go, interfețele sunt împărțite în două forme principale: EFace (Empty Interface) și IFace (Interface with Methods). Dintre acestea, EFace este conceptul identic cu tipul any discutat până acum.

Structură

EFace este o structură specială care există doar în cadrul runtime-ului limbajului Go, prin urmare, aici vom crea o imitație (mimic) pentru a o aborda.

 1type runtimeTypeMimic struct {
 2	Size_       uintptr
 3	PtrBytes    uintptr // numărul de octeți (prefix) din tipul care poate conține pointeri
 4	Hash        uint32  // hash-ul tipului; evită calculul în tabelele hash
 5	TFlag       uint8   // flag-uri suplimentare de informații despre tip
 6	Align_      uint8   // alinierea variabilei cu acest tip
 7	FieldAlign_ uint8   // alinierea câmpului structurii cu acest tip
 8	Kind_       uint8   // enumerare pentru C
 9	Equal       func(unsafe.Pointer, unsafe.Pointer) bool
10	GCData      *byte
11	Str         int32 // forma șir
12	PtrToThis   int32 // tip pentru pointerul către acest tip, poate fi zero
13}
14
15type eFaceMimic struct {
16	Type *runtimeTypeMimic
17	Data unsafe.Pointer
18}

EFace are două câmpuri:

  • Type: un pointer care conține metadatele despre tipul real al valorii.
  • Data: un pointer care conține valoarea reală.

Și EFace gestionează intern informațiile de tip prin structura runtimeType. Această structură include diverse metadate, cum ar fi dimensiunea, alinierea și valoarea hash a tipului.

În cadrul acesteia, aspectele la care trebuie să acordăm atenție sunt câmpurile Kind_, Hash și Equal. Kind_ indică tipul, iar Hash și Equal sunt utilizate pentru a compara tipurile și a calcula valorile hash în tabelele hash.

Încapsulare

Acum, să încapsulăm acest EFace pentru a facilita utilizarea sa.

 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}

Funcția de mai sus primește o variabilă de tip any, extrage structura internă EFace și returnează informațiile de tip într-o structură TypeInfo. Acum, putem accesa cu ușurință valoarea hash a tipului, tipul, funcția de comparație și altele, utilizând această TypeInfo.

Utilizare

Utilizarea este extrem de simplă. Trebuie doar să transmiteți o variabilă de tip any funcției 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	// Compară cele două valori folosind funcția Equal din informațiile de tip
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}

În exemplul de mai sus, valorile întregi și în virgulă mobilă sunt transmise funcției GetTypeInfo pentru a afișa informațiile de tip respective și pentru a compara valorile utilizând funcția Equal. Aceasta permite obținerea rapidă a informațiilor de tip și compararea tipurilor.

În plus, această metodă poate fi utilizată în mod similar pentru structuri, pointeri și interfețe și poate rezolva problema frecventă a nil-ului tipizat (typed nil). Hash ar fi alocat dacă există un tip, dar This ar fi 0 dacă este nil.

Concluzie

EFace joacă un rol important în gestionarea tipului any în limbajul Go. Aceasta permite procesarea flexibilă și rapidă a valorilor de diverse tipuri, precum și obținerea și utilizarea detaliată a informațiilor de tip. Cu toate acestea, deoarece este o funcționalitate ascunsă în runtime, trebuie să se țină cont că poate fi afectată de modificările specificațiilor limbajului.