GoSuda

Any не е Any, но Any е Any

By snowmerak views ...

Any

В езика Go типът any е псевдоним на interface{}, една магическа интерфейсна стойност, която може да съдържа всеки тип. С други думи, всеки тип може да бъде присвоен на променлива от тип any.

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

По този начин типът any може да съдържа стойности от различни типове, което позволява писането на гъвкав код. Въпреки това при използването на типа any има някои неща, на които трябва да се обърне внимание.

Type Assertion

За да се използва реалната стойност на променлива от тип any, трябва да се приложи туидентификация на типа (type assertion). Типовото утвърждаване служи за уведомяване на компилатора, че променливата от тип any е от определен тип.

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}

В горния пример a.(string) утвърждава, че a е от тип string. Ако a не е от тип string, ok ще бъде false и по този начин може да се предотврати паника на програмата.

Type Switch

Type switch (тип суич) позволява да се извършват различни действия в зависимост от реалния тип на променлива от тип any. Това е полезно, когато трябва да се обработват няколко типа.

 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}

В горния пример a.(type) проверява реалния тип на a и изпълнява съответния case.

Reflection

С помощта на пакета reflect в езика Go може динамично да се проверява и манипулира типът и стойността на променлива от тип any по време на изпълнение. Това е полезно при работа със сложни структури от данни.

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

В горния пример с помощта на пакета reflect можем да проверим типа и стойността на a по време на изпълнение.

EFace

В езика Go интерфейсите се разделят на две основни форми: EFace (Empty Interface) и IFace (Interface with Methods). От тях EFace е същата концепция като типа any, с който се занимавахме досега.

Структура

EFace е специална структура, която съществува само във вътрешната среда на изпълнение (runtime) на Go, затова тук ще създадем мимик (mimic), с който да работим.

 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}

EFace има две полета:

  • Type: указател, който съдържа метаданни за реалния тип на стойността.
  • Data: указател, който съдържа самата реална стойност.

Освен това EFace управлява информацията за типовете вътрешно чрез структурата runtimeType. Тази структура съдържа различни метаданни за типа, като размер, подравняване, хеш стойност и др.

Частите, на които трябва да обърнем внимание, са полетата Kind_, Hash и Equal. Kind_ показва вида на типа, а Hash и Equal се използват за сравняване на типове и изчисляване на хеш стойности в хеш таблиците.

Обвиване

Сега нека обвием този EFace, за да можем да използваме черната магия по-удобно.

 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}

Горната функция приема променлива от тип any, извлича вътрешната EFace структура, извлича от нея информацията за типа и я връща като структура TypeInfo. Сега с помощта на TypeInfo можем лесно да достъпим хеш стойността на типа, вида му, функцията за сравнение и други.

Употреба

Употребата е наистина лесна. Просто подаваме променлива от тип any на функцията 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}

В горния пример подаваме целочислена и дробна стойност на функцията GetTypeInfo, за да отпечатаме информацията за съответните типове, и използваме функцията Equal, за да сравним стойностите. По този начин можем по-бързо да получим информация за типа и да сравняваме типове.

Освен това този подход може да се прилага по същия начин за структури, указатели, интерфейси и др., и може да разреши често срещания проблем с типизиран nil (typed nil). Hash е зададен, ако типът съществува, докато This е 0, ако е nil, поради което това е възможно.

Заключение

EFace играе важна роля в работата с типа any в съвременния Go. Чрез него можем гъвкаво и бързо да обработваме стойности от различни типове и да получаваме и използваме подробна информация за типовете. Въпреки това, тъй като това е скрита функционалност на runtime, тя може да бъде повлияна от промени в спецификацията на езика, затова трябва да се внимава.