Halo, Habr! Saya mempersembahkan kepada Anda terjemahan artikel 
"The Laws of Reflection" dari pencipta bahasa.
Refleksi adalah kemampuan suatu program untuk mengeksplorasi strukturnya sendiri, terutama melalui tipe. Ini adalah bentuk metaprogramming dan sumber kebingungan.
Di Go, refleksi banyak digunakan, misalnya, dalam paket tes dan fmt. Di artikel ini, kami akan mencoba untuk menghilangkan "sihir" dengan menjelaskan cara kerja refleksi di Go.
Jenis dan Antarmuka
Karena refleksi didasarkan pada sistem tipe, mari menyegarkan pengetahuan kita tentang tipe di Go.
Go diketik secara statis. Setiap variabel memiliki satu dan hanya satu tipe statis yang diperbaiki pada waktu kompilasi: 
int, float32, *MyType, []byte ... Jika kita mendeklarasikan:
 type MyInt int var i int var j MyInt 
maka 
i adalah tipe 
int dan 
j adalah tipe 
MyInt . Variabel 
i dan 
j memiliki tipe statis yang berbeda dan, meskipun mereka memiliki tipe dasar yang sama, mereka tidak dapat ditugaskan satu sama lain tanpa konversi.
Salah satu kategori jenis penting adalah antarmuka, yang merupakan set metode tetap. Antarmuka dapat menyimpan nilai spesifik (non-antarmuka) selama nilai ini mengimplementasikan metode antarmuka. Sepasang contoh yang 
terkenal adalah 
io.Reader dan io.Writer , tipe Reader dan Writer dari 
paket io :
 
Dikatakan bahwa setiap jenis yang mengimplementasikan metode 
Read() atau 
Write() dengan tanda tangan ini mengimplementasikan masing-masing 
io.Reader atau 
io.Writer . Ini berarti bahwa variabel tipe 
io.Reader dapat berisi nilai apa pun dari tipe Read ():
 var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer) 
Penting untuk memahami bahwa 
r dapat diberi nilai apa pun yang mengimplementasikan 
io.Reader . Go diketik secara statis, dan tipe statis 
r adalah 
io.Reader .
Contoh yang sangat penting dari jenis antarmuka adalah antarmuka kosong:
 interface{} 
Ini adalah set kosong metode ∅ dan diimplementasikan dengan nilai apa pun.
Beberapa mengatakan antarmuka Go adalah variabel yang diketik secara dinamis, tetapi ini adalah kesalahan. Mereka diketik secara statis: variabel dengan tipe antarmuka selalu memiliki tipe statis yang sama, dan meskipun pada saat dijalankan nilai yang disimpan dalam variabel antarmuka dapat mengubah jenisnya, nilai ini akan selalu memenuhi antarmuka. (Tidak ada yang 
undefined , 
NaN atau hal-hal lain yang merusak logika program.)
Ini harus dipahami - refleksi dan antarmuka terkait erat.
Representasi internal antarmuka
Russ Cox menulis 
posting blog terperinci tentang pengaturan antarmuka di Go. Artikel yang tak kalah bagus 
tentang Habr'e . Tidak perlu mengulangi keseluruhan cerita, poin-poin utama disebutkan.
Variabel tipe antarmuka memegang pasangan: nilai spesifik yang ditetapkan untuk variabel, dan deskriptor tipe untuk nilai itu. Lebih tepatnya, nilainya adalah elemen data dasar yang mengimplementasikan antarmuka, dan tipe menggambarkan tipe lengkap elemen ini. Misalnya setelah
 var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty 
r berisi, secara skematis, pasangan 
(, ) --> (tty, *os.File) . Perhatikan bahwa 
*os.File jenis mengimplementasikan metode selain 
Read() ; bahkan jika nilai antarmuka hanya menyediakan akses ke metode Baca (), nilai di dalamnya membawa semua informasi tentang jenis nilai ini. Inilah mengapa kita dapat melakukan hal-hal seperti itu:
 var w io.Writer w = r.(io.Writer) 
Ekspresi dalam penugasan ini adalah pernyataan tipe; ia mengklaim bahwa elemen di dalam 
r juga mengimplementasikan 
io.Writer , dan karenanya kita dapat menugaskannya ke 
w . Setelah ditetapkan, 
w akan berisi pasangan 
(tty, *os.File) . Ini adalah pasangan yang sama dengan di 
r . Tipe statis dari antarmuka menentukan metode mana yang dapat dipanggil pada variabel antarmuka, meskipun serangkaian metode yang lebih luas dapat memiliki nilai spesifik di dalamnya.
Melanjutkan, kita dapat melakukan hal berikut:
 var empty interface{} empty = w 
dan nilai kosong dari bidang kosong lagi akan berisi pasangan yang sama 
(tty, *os.File) . Ini nyaman: antarmuka kosong dapat berisi nilai apa pun dan semua informasi yang kita perlukan darinya.
Kami tidak memerlukan pernyataan tipe di sini, karena diketahui bahwa 
w memenuhi antarmuka kosong. Dalam contoh di mana kami mentransfer nilai dari 
Reader ke 
Writer , kami perlu secara eksplisit menggunakan pernyataan tipe, karena metode 
Writer bukan merupakan subset dari 
Reader . Mencoba mengonversi nilai yang tidak cocok dengan antarmuka akan menyebabkan kepanikan.
Satu detail penting adalah bahwa pasangan di dalam antarmuka selalu memiliki formulir (nilai, tipe spesifik) dan tidak dapat memiliki formulir (nilai, antarmuka). Antarmuka tidak mendukung antarmuka sebagai nilai.
Sekarang kita siap untuk belajar refleksi.
Hukum refleksi pertama mencerminkan
- Refleksi meluas dari antarmuka ke refleksi objek.
Pada tingkat dasar, mencerminkan hanyalah sebuah mekanisme untuk memeriksa sepasang jenis dan nilai yang disimpan dalam variabel antarmuka. Untuk memulai, ada dua jenis yang perlu kita ketahui: 
reflect.Type dan 
reflect.Value . Kedua jenis ini menyediakan akses ke konten variabel antarmuka dan masing-masing dikembalikan oleh fungsi sederhana, reflect.TypeOf () dan reflect.ValueOf (). Mereka mengekstrak bagian dari arti antarmuka. (Selain itu, 
reflect.Value mudah untuk mendapatkan 
reflect.Type , tetapi jangan gabungkan konsep 
Value dan 
Type saat ini.)
Mari kita mulai dengan 
TypeOf() :
 package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) } 
Program akan menampilkan
type: float64Program ini mirip dengan melewatkan variabel 
float64 x sederhana untuk 
float64 x reflect.TypeOf() . Apakah Anda melihat antarmuka? Dan itu - 
reflect.TypeOf() menerima antarmuka kosong, sesuai dengan deklarasi fungsi:
 
Ketika kita memanggil 
reflect.TypeOf(x) , 
x pertama kali disimpan dalam antarmuka kosong, yang kemudian diteruskan sebagai argumen; 
reflect.TypeOf() membongkar antarmuka kosong ini untuk mengembalikan informasi jenis.
Fungsi 
reflect.ValueOf() , tentu saja, mengembalikan nilai (selanjutnya kami akan mengabaikan template dan fokus pada kode):
 var x float64 = 3.4 fmt.Println("value:", reflect.ValueOf(x).String()) 
akan dicetak
value: <float64 Value>(Kami memanggil metode 
String() secara eksplisit karena, secara default, paket fmt membongkar untuk 
reflect.Value dan mencetak nilai tertentu.)
reflect.Type dan 
reflect.Type keduanya memiliki banyak metode, yang memungkinkan Anda untuk menjelajahi dan memodifikasinya. Salah satu contoh penting adalah 
reflect.Value memiliki metode 
Type() yang mengembalikan tipe nilai. 
reflect.Type dan 
reflect.Value memiliki metode 
Kind() yang mengembalikan konstanta yang menunjukkan elemen primitif mana yang disimpan: 
Uint, Float64, Slice ... Konstanta ini dideklarasikan dalam enumerasi dalam paket mencerminkan. Metode 
Value dengan nama seperti 
Int() dan 
Float() memungkinkan kami untuk mengeluarkan nilai (seperti int64 dan float64) yang terlampir di dalam:
 var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) 
akan dicetak
 type: float64 kind is float64: true value: 3.4 
Ada juga metode seperti 
SetInt() dan 
SetFloat() , tetapi untuk menggunakannya kita perlu memahami settability, topik dari hukum refleksi ketiga.
Pustaka refleksi memiliki beberapa properti yang perlu Anda sorot. Pertama, untuk menjaga API tetap sederhana, metode 
Value "getter" dan "setter" bekerja pada tipe terbesar yang dapat berisi nilai: 
int64 untuk semua bilangan bulat yang 
int64 . Yaitu, metode 
Int() dari nilai 
Value mengembalikan 
int64 , dan nilai 
SetInt() mengambil 
int64 ; konversi ke tipe aktual mungkin diperlukan:
 var x uint8 = 'x' v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) x = uint8(v.Uint())  
akan
 type: uint8 kind is uint8: true 
Di sini 
v.Uint() akan mengembalikan 
uint64 , pernyataan tipe eksplisit diperlukan.
Properti kedua adalah bahwa 
Kind() mencerminkan objek menggambarkan tipe dasar, bukan tipe statis. Jika objek refleksi berisi nilai tipe integer yang ditentukan pengguna, seperti pada
 type MyInt int var x MyInt = 7 v := reflect.ValueOf(x)  
v.Kind() == reflect.Int , meskipun tipe statis 
x adalah 
MyInt , bukan 
int . Dengan kata lain, 
Kind() tidak dapat membedakan 
int dari 
MyInt , 
MyInt Type() . 
Kind hanya dapat menerima nilai tipe bawaan.
Hukum refleksi kedua mencerminkan
- Refleksi meluas dari objek pantulan ke antarmuka.
Seperti refleksi fisik, pantulan dalam Go menciptakan kebalikannya.
Memiliki 
reflect.Value , kita dapat mengembalikan nilai antarmuka menggunakan metode 
Interface() ; Metode ini mengemas informasi jenis dan nilai kembali ke antarmuka dan mengembalikan hasilnya:
 
bvt
Sebagai contoh:
 y := v.Interface().(float64)  
mencetak nilai 
float64 diwakili oleh objek refleksi 
v .
Namun, kita dapat melakukan yang lebih baik lagi. Argumen di 
fmt.Println() dan 
fmt.Printf() dilewatkan sebagai antarmuka kosong, yang kemudian dibongkar oleh paket fmt secara internal, seperti pada contoh sebelumnya. Oleh karena itu, semua yang diperlukan untuk mencetak konten 
reflect.Value dengan benar adalah meneruskan hasil dari metode 
Interface() ke fungsi output yang diformat:
 fmt.Println(v.Interface()) 
(Mengapa tidak 
fmt.Println(v) ? Karena 
v adalah tipe 
reflect.Value ; kami ingin mendapatkan nilai yang terkandung di dalamnya.) Karena nilai kami adalah 
float64 , kami bahkan dapat menggunakan format floating point jika kami mau:
 fmt.Printf("value is %7.1e\n", v.Interface()) 
akan menampilkan dalam kasus tertentu
3.4e+00Sekali lagi, tidak perlu 
v.Interface() tipe hasil 
v.Interface() di 
float64 ; nilai antarmuka kosong berisi informasi tentang nilai spesifik di dalamnya, dan 
fmt.Printf() mengembalikannya.
Singkatnya, metode 
Interface() adalah kebalikan dari fungsi 
ValueOf() , kecuali bahwa hasilnya selalu dari 
interface{} tipe statis 
interface{} .
Ulangi: Refleksi meluas dari nilai antarmuka ke objek refleksi dan sebaliknya.
Hukum ketiga refleksi refleksi
- Untuk mengubah objek refleksi, nilainya harus dapat disetel.
Hukum ketiga adalah yang paling halus dan membingungkan. Kami mulai dengan prinsip pertama.
Kode ini tidak berfungsi, tetapi perlu diperhatikan.
 var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)  
Jika Anda menjalankan kode ini, kode itu akan macet karena panik dengan pesan penting:
panic: reflect.Value.SetFloatMasalahnya bukan bahwa 
7.1 literal tidak ditangani; inilah yang 
v tidak dapat diinstal. 
reflect.Value adalah properti dari 
reflect.Value , dan tidak setiap 
reflect.Value memilikinya.
Metode 
reflect.Value.CanSet() yang ditetapkan; dalam kasus kami:
 var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet()) 
akan dicetak:
settability of v: falseTerjadi kesalahan saat memanggil metode 
Set() pada nilai yang tidak dikelola. Tapi apa itu instalabilitas?
Keberlanjutan agak mirip dengan addressability, tetapi lebih ketat. Ini adalah properti tempat objek refleksi dapat mengubah nilai yang disimpan yang digunakan untuk membuat objek refleksi. Keberlanjutan ditentukan oleh apakah objek refleksi berisi elemen sumber, atau hanya salinannya. Ketika kita menulis:
 var x float64 = 3.4 v := reflect.ValueOf(x) 
kami meneruskan salinan 
x ke 
reflect.ValueOf() , sehingga antarmuka dibuat sebagai argumen untuk 
reflect.ValueOf() - ini adalah salinan 
x , bukan 
x itu sendiri. Jadi, jika pernyataan:
 v.SetFloat(7.1) 
jika dijalankan, itu tidak akan memperbarui 
x , meskipun 
v sepertinya itu dibuat dari 
x . Sebagai gantinya, ia akan memperbarui salinan 
x tersimpan di dalam nilai 
v , dan 
x itu sendiri tidak akan terpengaruh. Ini dilarang agar tidak menimbulkan masalah, dan instalabilitas adalah properti yang digunakan untuk mencegah masalah.
Ini seharusnya tidak aneh. Ini adalah situasi umum dalam pakaian yang tidak biasa. Pertimbangkan untuk meneruskan 
x ke fungsi:
f(x)Kami tidak berharap 
f() dapat mengubah 
x , karena kami melewati salinan nilai 
x , bukan 
x itu sendiri. Jika kita ingin 
f() secara langsung mengubah 
x , kita harus meneruskan sebuah pointer ke 
x ke fungsi kita:
f(&x)Ini mudah dan akrab, dan refleksi bekerja dengan cara yang sama. Jika kita ingin mengubah 
x menggunakan refleksi, kita harus menyediakan pointer perpustakaan dengan nilai yang ingin kita ubah.
Ayo lakukan. Pertama, kita menginisialisasi 
x seperti biasa, dan kemudian membuat 
reflect.Value p yang menunjuk ke sana.
 var x float64 = 3.4 p := reflect.ValueOf(&x)  
akan menampilkan
type of p: *float64
settability of p: falseObjek Refleksi 
p tidak dapat diatur, tetapi bukan 
p yang ingin kita atur, itu adalah pointer 
*p . Untuk mendapatkan poin 
p , kita memanggil metode 
Value.Elem() , yang mengambil nilai secara tidak langsung melalui pointer, dan menyimpan hasilnya dalam 
reflect.Value v :
 v := p.Elem() fmt.Println("settability of v:", v.CanSet()) 
Sekarang 
v adalah objek yang dapat diinstal;
settability of v: truedan karena itu merepresentasikan 
x , kita akhirnya bisa menggunakan 
v.SetFloat() untuk mengubah nilai 
x :
 v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x) 
kesimpulan seperti yang diharapkan
7.1
7.1Refleksi mungkin sulit untuk dipahami, tetapi ia melakukan persis seperti apa bahasa itu, meskipun dengan bantuan 
reflect.Type dan 
reflection.Value , yang dapat menyembunyikan apa yang terjadi. Perlu diingat bahwa 
reflection.Value memerlukan alamat variabel untuk mengubahnya.
Struktur
Dalam contoh kita sebelumnya, 
v bukan pointer, itu hanya diturunkan darinya. Cara umum untuk membuat situasi ini adalah menggunakan refleksi untuk mengubah bidang struktur. Selama kita memiliki alamat struktur, kita dapat mengubah bidangnya.
Berikut adalah contoh sederhana yang menganalisis nilai struktur 
t . Kami membuat objek refleksi dengan alamat struktur untuk memodifikasinya nanti. Kemudian atur typeOfT ke tipenya dan lakukan iterasi pada field menggunakan pemanggilan metode sederhana (lihat 
paket untuk penjelasan terperinci ). Perhatikan bahwa kami mengekstraksi nama bidang dari tipe struktur, tetapi bidang itu sendiri adalah 
reflect.Value biasa.
 type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) } 
Program akan menampilkan
0: A int = 23
1: B string = skidooSatu hal lagi tentang installabilitas ditunjukkan di sini: nama bidang 
T dalam huruf besar (diekspor), karena hanya bidang yang diekspor yang dapat diatur.
Karena 
s berisi objek refleksi yang dapat diinstal, kita dapat mengubah bidang struktur.
 s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t) 
Hasil:
t is now {77 Sunset Strip}Jika kita mengubah program sehingga 
s dibuat dari 
t daripada 
&t , panggilan ke 
SetInt() dan 
SetString() akan berakhir dengan panik, karena bidang 
t tidak akan dapat diatur.
Kesimpulan
Ingat hukum refleksi:
- Refleksi meluas dari antarmuka ke refleksi objek.
- Refleksi meluas dari refleksi objek ke antarmuka.
- Untuk mengubah objek refleksi, nilai harus ditetapkan.
Diposting oleh 
Rob Pike .