GoSuda

Any bukanlah Any, melainkan Any adalah Any

By snowmerak views ...

Any

Dalam bahasa Go, tipe any adalah alias dari interface{}, sebuah antarmuka ajaib yang dapat menampung semua tipe. Artinya, nilai dengan tipe apa pun dapat ditetapkan ke variabel bertipe any.

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

Dengan demikian, tipe any dapat menampung nilai dari berbagai tipe sehingga memungkinkan penulisan kode yang fleksibel. Namun, terdapat beberapa hal yang perlu diperhatikan saat menggunakan tipe any.

Type Assertion

Untuk menggunakan nilai sebenarnya dari variabel bertipe any, Anda harus menggunakan type assertion (type assertion). Type assertion berperan memberi tahu kompilator bahwa variabel bertipe any tersebut merupakan tipe tertentu.

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}

Pada contoh di atas, a.(string) menegaskan bahwa a bertipe string. Jika a bukan bertipe string, maka ok akan bernilai false, sehingga program dapat dicegah dari kondisi panic.

Type Switch

Type switch (type switch) memungkinkan Anda menjalankan perilaku yang berbeda sesuai dengan tipe sebenarnya dari variabel bertipe any. Hal ini berguna ketika harus menangani beberapa tipe.

 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}

Pada contoh di atas, a.(type) memeriksa tipe sebenarnya dari a, lalu menjalankan case yang sesuai dengan tipe tersebut.

Reflection

Dengan menggunakan paket reflect dalam bahasa Go, Anda dapat memeriksa dan memanipulasi tipe serta nilai dari variabel bertipe any secara dinamis saat runtime. Hal ini berguna ketika menangani struktur data yang kompleks.

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

Pada contoh di atas, paket reflect digunakan untuk memeriksa tipe dan nilai dari a saat runtime.

EFace

Dalam bahasa Go, interface terbagi menjadi dua bentuk utama: EFace (Empty Interface) dan IFace (Interface with Methods). Di antara keduanya, EFace adalah konsep yang sama dengan tipe any yang telah kita bahas sejauh ini.

Struktur

EFace adalah struktur khusus yang hanya ada di dalam runtime Go, sehingga di sini kita akan membuat mimic (mimic) untuk menanganinya.

 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 memiliki dua field:

  • Type: pointer yang berisi metadata tentang tipe sebenarnya dari nilai.
  • Data: pointer yang berisi nilai sebenarnya.

Selain itu, EFace secara internal mengelola informasi tipe melalui struktur runtimeType. Struktur ini memuat berbagai metadata, seperti ukuran tipe, alignment, dan nilai hash.

Bagian yang perlu kita perhatikan dalam struktur ini adalah field Kind_, Hash, dan Equal. Kind_ menunjukkan jenis tipe, sedangkan Hash dan Equal digunakan untuk membandingkan tipe dan menghitung nilai hash dalam hash table.

Pembungkusan

Sekarang mari kita bungkus EFace ini agar lebih mudah digunakan untuk melakukan black magic.

 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}

Fungsi di atas menerima variabel bertipe any, mengekstrak struktur EFace di dalamnya, lalu mengambil informasi tipe dari sana dan mengembalikannya sebagai struktur TypeInfo. Dengan TypeInfo ini, Anda dapat dengan mudah mengakses nilai hash, jenis, fungsi perbandingan, dan lainnya dari tipe tersebut.

Penggunaan

Penggunaannya sangat mudah. Cukup berikan variabel bertipe any ke fungsi 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	// Bandingkan kedua nilai menggunakan fungsi Equal dari informasi tipe
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}

Pada contoh di atas, nilai integer dan floating-point dikirim ke fungsi GetTypeInfo untuk mencetak informasi tipe masing-masing, lalu fungsi Equal digunakan untuk membandingkan nilai-nilai tersebut. Dengan cara ini, informasi tipe dapat diperoleh dan tipe dibandingkan dengan lebih cepat.

Selain itu, metode ini juga dapat digunakan secara sama untuk struct, pointer, interface, dan lainnya, serta dapat mengatasi masalah typed nil (typed nil) yang cukup sering terjadi. Field Hash akan terisi jika tipenya ada, sedangkan This akan bernilai 0 jika nil.

Kesimpulan

EFace memegang peranan penting dalam menangani tipe any di Go saat ini. Melalui EFace, nilai dari berbagai tipe dapat diproses secara fleksibel dan cepat, serta informasi tipe dapat diperoleh dan dimanfaatkan secara rinci. Namun, karena ini merupakan fitur tersembunyi di dalam runtime, perubahan pada spesifikasi bahasa dapat memengaruhinya sehingga perlu diwaspadai.