الكتاب الأساسي

الانعكاس في Go

Go لغة ساكنة (static language) ذات دعم جيد للانعكاس. سيشرح باقي هذا المقال وظائف الانعكاس الموفَّرة في الحزمة القياسية reflect.

من المفيد جدًا قراءة مقالَي نظرة عامة على نظام الأنواع في Go والواجهات في Go قبل قراءة باقي هذا المقال.

نظرة عامة على الانعكاس في Go

يجلب الانعكاس في Go العديد من الوظائف الديناميكية إلى برمجة Go. وتعتمد العديد من حزم الشيفرة القياسية، مثل حزمتَي fmt وencoding، اعتمادًا كبيرًا على وظائف الانعكاس.

يمكننا فحص قيم Go من خلال قيم النوعين Type وValue المعرَّفين في الحزمة القياسية reflect. وسيعرض باقي هذا المقال بعض الأمثلة على كيفية استخدام قيم هذين النوعين.

أحد أهداف تصميم الانعكاس في Go هو أن تكون أي عملية غير انعكاسية قابلة للتطبيق أيضًا بالطرق الانعكاسية. ولأسباب شتى، لم يتحقق هذا الهدف بنسبة 100 بالمئة. ومع ذلك، يمكن الآن تطبيق معظم العمليات غير الانعكاسية بالطرق الانعكاسية. ومن ناحية أخرى، يمكننا عبر الطرق الانعكاسية تنفيذ بعض العمليات التي يستحيل تحقيقها بالطرق غير الانعكاسية. وستُذكر في الأقسام التالية العمليات التي لا يمكن تحقيقها إلا بالطرق الانعكاسية.

نوع reflect.Type وقيمه

في Go، يمكننا إنشاء قيمة reflect.Type من أي قيمة غير واجهية عبر استدعاء الدالة reflect.TypeOf. وتمثّل قيمة reflect.Type الناتجة نوع القيمة غير الواجهية. وبالتأكيد، يمكننا أيضًا تمرير قيمة واجهية إلى استدعاء الدالة reflect.TypeOf، لكن الاستدعاء سيعيد قيمة reflect.Type تمثّل النوع الديناميكي للقيمة الواجهية. في الواقع، للدالة reflect.TypeOf وسيط واحد فقط من النوع interface{}، وهي تعيد دائمًا قيمة reflect.Type تمثّل النوع الديناميكي للوسيط الواجهي الوحيد. وللحصول على قيمة reflect.Type تمثّل نوع واجهة باستخدام الدالة reflect.TypeOf، يجب علينا استخدام طرق غير مباشرة ستُقدَّم أدناه لتحقيق هذا الهدف.

منذ Go 1.22، يمكننا أيضًا استخدام الدالة reflect.TypeFor للحصول على قيمة reflect.Type تمثّل نوعًا معروفًا في وقت التصريف. وقد يكون النوع المعروف في وقت التصريف إما غير واجهي أو واجهيًا.

نوع reflect.Type هو نوع واجهة. وهو يحدد عدة طرق. يمكننا استدعاء هذه الطرق لفحص معلومات النوع الذي تمثّله قيمة المستقبِل reflect.Type. وبعض هذه الطرق ينطبق على جميع أصناف الأنواع، وبعضها خاص بصنف واحد أو عدة أصناف. يُرجى قراءة توثيق كل طريقة للتفاصيل. وسيُنتج استدعاء إحدى هذه الطرق عبر قيمة مستقبِل reflect.Type غير مناسبة panic.

مثال:


package main

import "fmt"
import "reflect"

func main() {
	type A = [16]int16
	var c <-chan map[A][]byte
	tc := reflect.TypeOf(c)
	fmt.Println(tc.Kind())    // chan
	fmt.Println(tc.ChanDir()) // <-chan
	tm := tc.Elem()
	ta, tb := tm.Key(), tm.Elem()
	// السطر التالي يطبع: map array slice
	fmt.Println(tm.Kind(), ta.Kind(), tb.Kind())
	tx, ty := ta.Elem(), tb.Elem()

	// byte هو اسم بديل لـ uint8
	fmt.Println(tx.Kind(), ty.Kind()) // int16 uint8
	fmt.Println(tx.Bits(), ty.Bits()) // 16 8
	fmt.Println(tx.ConvertibleTo(ty)) // true
	fmt.Println(tb.ConvertibleTo(ta)) // false

	// نوعا الشريحة والخريطة غير قابلين للمقارنة.
	fmt.Println(tb.Comparable()) // false
	fmt.Println(tm.Comparable()) // false
	fmt.Println(ta.Comparable()) // true
	fmt.Println(tc.Comparable()) // true
}

يوجد 26 صنفًا من الأنواع في Go.

في المثال أعلاه، نستخدم الطريقة Elem للحصول على أنواع العناصر لبعض أنواع الحاويات (نوع قناة ونوع خريطة ونوع شريحة ونوع مصفوفة). في الواقع، يمكننا أيضًا استخدام هذه الطريقة للحصول على النوع الأساسي لنوع مؤشر. مثلاً،


package main

import "fmt"
import "reflect"

type T []interface{m()}
func (T) m() {}

func main() {
	tp := reflect.TypeOf(new(interface{}))
	tt := reflect.TypeOf(T{})
	fmt.Println(tp.Kind(), tt.Kind()) // ptr slice

	// احصل على نوعَي واجهة بشكل غير مباشر.
	ti, tim := tp.Elem(), tt.Elem()
	// السطر التالي يطبع: interface interface
	fmt.Println(ti.Kind(), tim.Kind())

	fmt.Println(tt.Implements(tim))  // true
	fmt.Println(tp.Implements(tim))  // false
	fmt.Println(tim.Implements(tim)) // true

	// كل الأنواع تُنفِّذ أي نوع واجهة فارغة.
	fmt.Println(tp.Implements(ti))  // true
	fmt.Println(tt.Implements(ti))  // true
	fmt.Println(tim.Implements(ti)) // true
	fmt.Println(ti.Implements(ti))  // true
}

يوضح المثال أعلاه أيضًا كيفية الحصول (بشكل غير مباشر) على قيمة reflect.Type تمثّل نوع واجهة.

يمكننا الحصول على جميع أنواع الحقول (لنوع بنية) ومعلومات طرق نوع ما عبر الانعكاس. ويمكننا أيضًا الحصول على معلومات أنواع الوسائط والنتائج لنوع دالة عبر الانعكاس.


package main

import "fmt"
import "reflect"

type F func(string, int) bool
func (f F) m(s string) bool {
	return f(s, 32)
}
func (f F) M() {}

type I interface{m(s string) bool; M()}

func main() {
	var x struct {
		F F
		i I
	}
	tx := reflect.TypeOf(x)
	fmt.Println(tx.Kind())        // struct
	fmt.Println(tx.NumField())    // 2
	fmt.Println(tx.Field(1).Name) // i
	// مسار الحزمة خاصية أصلية للمحدِّدات
	// غير المُصدَّرة (الحقول أو الطرق).
	fmt.Println(tx.Field(0).PkgPath) //
	fmt.Println(tx.Field(1).PkgPath) // main

	tf, ti := tx.Field(0).Type, tx.Field(1).Type
	fmt.Println(tf.Kind())               // func
	fmt.Println(tf.IsVariadic())         // false
	fmt.Println(tf.NumIn(), tf.NumOut()) // 2 1
	t0, t1, t2 := tf.In(0), tf.In(1), tf.Out(0)
	// السطر التالي يطبع: string int bool
	fmt.Println(t0.Kind(), t1.Kind(), t2.Kind())

	fmt.Println(tf.NumMethod(), ti.NumMethod()) // 1 2
	fmt.Println(tf.Method(0).Name)              // M
	fmt.Println(ti.Method(1).Name)              // m
	_, ok1 := tf.MethodByName("m")
	_, ok2 := ti.MethodByName("m")
	fmt.Println(ok1, ok2) // false true
}

من المثال أعلاه، يمكننا أن نجد ما يلي:

  1. بالنسبة للأنواع غير الواجهية، لا تعيد reflect.Type.NumMethod سوى عدد الطرق المُصدَّرة (بما فيها المعلنة ضمنيًا) لنوع ما. ولا يمكننا الحصول على معلومات طريقة غير مُصدَّرة باستخدام الطريقة reflect.Type.MethodByName. أما بالنسبة لأنواع الواجهات فلا توجد هذه القيود (لم تُذكر هذه الحقيقة في توثيق الطريقتين قبل Go 1.16). وينطبق هذا الوضع أيضًا على الطرق المقابلة لنوع reflect.Value المُقدَّم في القسم التالي.
  2. على الرغم من أن استدعاء الطريقة reflect.Type.NumField يعيد عدد جميع الحقول (بما فيها غير المُصدَّرة) لنوع بنية، فإن استخدام الطريقة reflect.Type.FieldByName للحصول على معلومات حقل غير مُصدَّر ليس فكرة جيدة.

يمكننا فحص وسوم حقول البنيات عبر الانعكاس. ونوع وسوم حقول البنيات هو reflect.StructTag، وله طريقتان، Get وLookup، لفحص أزواج المفتاح-القيمة المحددة في وسوم الحقول. مثال على فحص وسوم حقول البنيات:


package main

import "fmt"
import "reflect"

type T struct {
	X    int  `max:"99" min:"0" default:"0"`
	Y, Z bool `optional:"yes"`
}

func main() {
	t := reflect.TypeOf(T{})
	x := t.Field(0).Tag
	y := t.Field(1).Tag
	z := t.Field(2).Tag
	fmt.Println(reflect.TypeOf(x)) // reflect.StructTag
	// v نص
	v, present := x.Lookup("max")
	fmt.Println(len(v), present)      // 2 true
	fmt.Println(x.Get("max"))         // 99
	fmt.Println(x.Lookup("optional")) //  false
	fmt.Println(y.Lookup("optional")) // yes true
	fmt.Println(z.Lookup("optional")) // yes true
}

ملاحظة:

  • قد لا تحتوي مفاتيح الوسوم على محارف المسافة (قيمة Unicode 32)، والاقتباس (قيمة Unicode 34)، والنقطتين الرأسيتين (قيمة Unicode 58).
  • لتشكيل زوج مفتاح-قيمة صالح، لا يُسمح بوجود محارف مسافة بعد الفاصلة المنقوطة في زوج المفتاح-القيمة المفترض. لذا فإنّ optional: "yes" لا تشكّل أزواج مفتاح-قيمة.
  • محارف المسافة في قيم الوسوم مهمة (لا تُتجاهل). لذا فإنّ json:"author, omitempty"، json:" author,omitempty" و json:"author,omitempty" مختلفة عن بعضها البعض.
  • يجب أن يظهر كل وسم حقل بنية في سطر واحد ليكون ذا معنى كامل.

إلى جانب الدالة reflect.TypeOf، يمكننا أيضًا استخدام بعض الدوال الأخرى في الحزمة القياسية reflect لإنشاء قيم reflect.Type تمثّل بعض أنواع التركيب غير المسماة.


package main

import "fmt"
import "reflect"

func main() {
	ta := reflect.ArrayOf(5, reflect.TypeOf(123))
	fmt.Println(ta) // [5]int
	tc := reflect.ChanOf(reflect.SendDir, ta)
	fmt.Println(tc) // chan<- [5]int
	tp := reflect.PtrTo(ta)
	fmt.Println(tp) // *[5]int
	ts := reflect.SliceOf(tp)
	fmt.Println(ts) // []*[5]int
	tm := reflect.MapOf(ta, tc)
	fmt.Println(tm) // map[[5]int]chan<- [5]int
	tf := reflect.FuncOf([]reflect.Type{ta},
				[]reflect.Type{tp, tc}, false)
	fmt.Println(tf) // func([5]int) (*[5]int, chan<- [5]int)
	tt := reflect.StructOf([]reflect.StructField{
		{Name: "Age", Type: reflect.TypeOf("abc")},
	})
	fmt.Println(tt)            // struct { Age string }
	fmt.Println(tt.NumField()) // 1
}

هناك المزيد من طرق reflect.Type التي لم تُستخدم في الأمثلة أعلاه، يُرجى قراءة توثيق حزمة reflect لمعرفة استخداماتها.

ملاحظة، حتى الآن (Go 1.25):

  • لا توجد طرق لإنشاء أنواع واجهات عبر الانعكاس.
  • على الرغم من أنه يمكننا إنشاء نوع بنية بحقول تضمين عبر الانعكاس، فإن هذه الطريقة تنطوي على مزالق كثيرة.

نوع reflect.Value وقيمه

بالمثل، يمكننا إنشاء قيمة reflect.Value من أي قيمة غير واجهية عبر استدعاء الدالة reflect.ValueOf. وتمثّل قيمة reflect.Value الناتجة القيمة غير الواجهية. وكما في الدالة reflect.TypeOf، فللدالة reflect.ValueOf أيضًا وسيط واحد فقط من النوع interface{}. وعند تمرير معطى واجهي إلى استدعاء الدالة reflect.ValueOf، سيعيد الاستدعاء قيمة reflect.Value تمثّل القيمة الديناميكية للمعطى الواجهي. وللحصول على قيمة reflect.Value تمثّل قيمة واجهية، يجب علينا استخدام طرق غير مباشرة ستُقدَّم أدناه لتحقيق هذا الهدف.

غالبًا ما تُسمّى القيمة التي تمثّلها قيمة reflect.Value المسماة v القيمة الأساسية (underlying value) لـ v.

هناك طرق وفيرة معلنة لنوع reflect.Value. يمكننا استدعاء هذه الطرق لفحص معلومات (والتلاعب بـ) القيمة الأساسية لقيمة المستقبِل reflect.Value. وبعض هذه الطرق ينطبق على جميع أصناف القيم، وبعضها خاص بصنف واحد أو عدة أصناف. يُرجى قراءة توثيق الحزمة القياسية reflect للتفاصيل. وسيُنتج استدعاء طريقة خاصة بصنف معيّن بقيمة مستقبِل reflect.Value غير مناسبة panic.

تعيد الطريقة CanSet لقيمة reflect.Value ما إذا كانت القيمة الأساسية لقيمة reflect.Value قابلة للتعديل (يمكن الإسناد إليها) أم لا. وإذا كانت قيمة Go قابلة للتعديل، يمكننا استدعاء الطريقة Set لقيمة reflect.Value المقابلة لتعديل قيمة Go. ملاحظة: قيم reflect.Value التي تعيدها استدعاءات الدالة reflect.ValueOf مباشرةً هي دائمًا للقراءة فقط.

مثال:


package main

import "fmt"
import "reflect"

func main() {
	n := 123
	p := &n
	vp := reflect.ValueOf(p)
	fmt.Println(vp.CanSet(), vp.CanAddr()) // false false
	vn := vp.Elem() // احصل على القيمة المشار إليها بواسطة vp
	fmt.Println(vn.CanSet(), vn.CanAddr()) // true true
	vn.Set(reflect.ValueOf(789)) // <=> vn.SetInt(789)
	fmt.Println(n)               // 789
}

لا يمكن تعديل الحقول غير المُصدَّرة لقيم البنيات عبر الانعكاس.


package main

import "fmt"
import "reflect"

func main() {
	var s struct {
		X interface{} // حقل مُصدَّر
		y interface{} // حقل غير مُصدَّر
	}
	vp := reflect.ValueOf(&s)
	// إذا كانت vp تمثّل مؤشرًا، فالسطر
	// التالي مكافئ لـ "vs := vp.Elem()".
	vs := reflect.Indirect(vp)
	// تمثّل كل من vx وvy قيمتين واجهيتين.
	vx, vy := vs.Field(0), vs.Field(1)
	fmt.Println(vx.CanSet(), vx.CanAddr()) // true true
	// يمكن أخذ عنوان vy لكنها غير قابلة للتعديل.
	fmt.Println(vy.CanSet(), vy.CanAddr()) // false true
	vb := reflect.ValueOf(123)
	vx.Set(vb)     // لا بأس، لأن vx قابلة للتعديل
	// vy.Set(vb)  // سيحدث panic، لأن vy غير قابلة للتعديل
	fmt.Println(s) // {123 <nil>}
	fmt.Println(vx.IsNil(), vy.IsNil()) // false true
}

من المثالين أعلاه، يمكننا أن نتعلّم أن هناك طريقتين للحصول على قيمة reflect.Value تمثّل القيمة التي تشير إليها القيمة الأساسية (قيمة مؤشر) لقيمة reflect.Value أخرى.

  1. إحدى الطريقتين هي استدعاء الطريقة Elem لقيمة reflect.Value تمثّل قيمة المؤشر.
  2. والطريقة الأخرى هي تمرير قيمة reflect.Value تمثّل قيمة المؤشر إلى استدعاء الدالة reflect.Indirect. (إذا كان المعطى الممرَّر إلى استدعاء الدالة reflect.Indirect لا يمثّل قيمة مؤشر، فإن الاستدعاء يعيد نسخة من المعطى.)

ملاحظة: يمكن أيضًا استخدام الطريقة reflect.Value.Elem للحصول على قيمة reflect.Value تمثّل القيمة الديناميكية لقيمة واجهية. مثلاً،


package main

import "fmt"
import "reflect"

func main() {
	var z = 123
	var y = &z
	var x interface{} = y
	v := reflect.ValueOf(&x)
	vx := v.Elem()
	vy := vx.Elem()
	vz := vy.Elem()
	vz.Set(reflect.ValueOf(789))
	fmt.Println(z) // 789
}

تعلن الحزمة القياسية reflect أيضًا بعض الدوال المتعلقة بـ reflect.Value. وتقابل كل دالة من هذه الدوال دالة مدمجة أو وظيفة غير انعكاسية. ويوضح المثال التالي كيفية ربط دالة جنيسة مخصصة (من صنف معيّن) بقيم دوال مختلفة.


package main

import "fmt"
import "reflect"

func InvertSlice(args []reflect.Value) []reflect.Value {
	inSlice, n := args[0], args[0].Len()
	outSlice := reflect.MakeSlice(inSlice.Type(), 0, n)
	for i := n-1; i >= 0; i-- {
		element := inSlice.Index(i)
		outSlice = reflect.Append(outSlice, element)
	}
	return []reflect.Value{outSlice}
}

func Bind(p interface{},
		f func ([]reflect.Value) []reflect.Value) {
	// invert يمثل قيمة دالة.
	invert := reflect.ValueOf(p).Elem()
	invert.Set(reflect.MakeFunc(invert.Type(), f))
}

func main() {
	var invertInts func([]int) []int
	Bind(&invertInts, InvertSlice)
	fmt.Println(invertInts([]int{2, 3, 5})) // [5 3 2]

	var invertStrs func([]string) []string
	Bind(&invertStrs, InvertSlice)
	fmt.Println(invertStrs([]string{"Go", "C"})) // [C Go]
}

إذا كانت القيمة الأساسية لقيمة reflect.Value قيمة دالة، فيمكننا استدعاء الطريقة Call لقيمة reflect.Value لاستدعاء الدالة الأساسية.


package main

import "fmt"
import "reflect"

type T struct {
	A, b int
}

func (t T) AddSubThenScale(n int) (int, int) {
	return n * (t.A + t.b), n * (t.A - t.b)
}

func main() {
	t := T{5, 2}
	vt := reflect.ValueOf(t)
	vm := vt.MethodByName("AddSubThenScale")
	results := vm.Call([]reflect.Value{reflect.ValueOf(3)})
	fmt.Println(results[0].Int(), results[1].Int()) // 21 9

	neg := func(x int) int {
		return -x
	}
	vf := reflect.ValueOf(neg)
	fmt.Println(vf.Call(results[:1])[0].Int()) // -21
	fmt.Println(vf.Call([]reflect.Value{
		vt.FieldByName("A"), // panic عند التغيير إلى "b"
	})[0].Int()) // -5
}

يُرجى ملاحظة أنه لا ينبغي استخدام الحقول غير المُصدَّرة كمعطيات لاستدعاءات الانعكاس. وإذا استُبدل السطر vt.FieldByName("A") في المثال أعلاه بـ vt.FieldByName("b")، فسيحدث panic.

مثال انعكاسي على قيم الخرائط.


package main

import "fmt"
import "reflect"

func main() {
	valueOf := reflect.ValueOf
	m := map[string]int{"Unix": 1973, "Windows": 1985}
	v := valueOf(m)
	// معطى Value ثانٍ صفري يعني حذف عنصر.
	v.SetMapIndex(valueOf("Windows"), reflect.Value{})
	v.SetMapIndex(valueOf("Linux"), valueOf(1991))
	for i := v.MapRange(); i.Next(); {
		fmt.Println(i.Key(), "\t:", i.Value())
	}
}

يُرجى ملاحظة أن الطريقة MapRange مدعومة منذ Go 1.12.

مثال انعكاسي على قيم القنوات.


package main

import "fmt"
import "reflect"

func main() {
	c := make(chan string, 2)
	vc := reflect.ValueOf(c)
	vc.Send(reflect.ValueOf("C"))
	succeeded := vc.TrySend(reflect.ValueOf("Go"))
	fmt.Println(succeeded) // true
	succeeded = vc.TrySend(reflect.ValueOf("C++"))
	fmt.Println(succeeded) // false
	fmt.Println(vc.Len(), vc.Cap()) // 2 2
	vs, succeeded := vc.TryRecv()
	fmt.Println(vs.String(), succeeded) // C true
	vs, sentBeforeClosed := vc.Recv()
	fmt.Println(vs.String(), sentBeforeClosed) // Go true
	vs, succeeded = vc.TryRecv()
	fmt.Println(vs.String()) // <invalid Value>
	fmt.Println(succeeded)   // false
}

تقابل الطريقتان TrySend وTryRecv كتل شيفرة التحكم select ذات الحالة الواحدة والافتراضي الواحد.

يمكننا استخدام الدالة reflect.Select لمحاكاة كتلة شيفرة select بعدد ديناميكي من فروع case في وقت التشغيل.


package main

import "fmt"
import "reflect"

func main() {
	c := make(chan int, 1)
	vc := reflect.ValueOf(c)
	succeeded := vc.TrySend(reflect.ValueOf(123))
	fmt.Println(succeeded, vc.Len(), vc.Cap()) // true 1 1

	vSend, vZero := reflect.ValueOf(789), reflect.Value{}
	branches := []reflect.SelectCase{
		{Dir: reflect.SelectDefault, Chan: vZero, Send: vZero},
		{Dir: reflect.SelectRecv, Chan: vc, Send: vZero},
		{Dir: reflect.SelectSend, Chan: vc, Send: vSend},
	}
	selIndex, vRecv, sentBeforeClosed := reflect.Select(branches)
	fmt.Println(selIndex)         // 1
	fmt.Println(sentBeforeClosed) // true
	fmt.Println(vRecv.Int())      // 123
	vc.Close()
	// احذف فرع الإرسال case هذه المرة،
	// لأنه قد يسبب panic.
	selIndex, _, sentBeforeClosed = reflect.Select(branches[:2])
	fmt.Println(selIndex, sentBeforeClosed) // 1 false
}

قد لا تكون القيم الأساسية المقابلة لبعض قيم reflect.Value شيئًا. مثلاً، قيم reflect.Value الصفرية.


package main

import "reflect"
import "fmt"

func main() {
	var z reflect.Value // قيمة Value صفرية
	fmt.Println(z)      // <invalid reflect.Value>
	v := reflect.ValueOf((*int)(nil)).Elem()
	fmt.Println(v)      // <invalid reflect.Value>
	fmt.Println(v == z) // true
	var i = reflect.ValueOf([]interface{}{nil}).Index(0)
	fmt.Println(i)             // <nil>
	fmt.Println(i.Elem() == z) // true
	fmt.Println(i.Elem())      // <invalid reflect.Value>
}

بالنسبة لقيمة Go، يمكننا استخدام الدالة reflect.ValueOf لإنشاء قيمة reflect.Value تمثّل قيمة Go، بمساعدة interface{}. والعملية العكسية مشابهة، إذ يمكننا استدعاء الطريقة Interface لقيمة reflect.Value للحصول على قيمة interface{}، ثم إجراء تأكيد نوع (type assert) على قيمة interface{} للحصول على قيمة Go التي تمثّلها (أي القيمة الأساسية لـ) قيمة reflect.Value. لكن يُرجى ملاحظة أن استدعاء الطريقة Interface لقيمة reflect.Value تمثّل حقلًا غير مُصدَّر يسبب panic.


package main

import (
	"fmt"
	"reflect"
	"time"
)

func main() {
	vx := reflect.ValueOf(123)
	vy := reflect.ValueOf("abc")
	vz := reflect.ValueOf([]bool{false, true})
	vt := reflect.ValueOf(time.Time{})

	x := vx.Interface().(int)
	y := vy.Interface().(string)
	z := vz.Interface().([]bool)
	m := vt.MethodByName("IsZero").Interface().(func() bool)
	fmt.Println(x, y, z, m()) // 123 abc [false true] true

	type T struct {x int}
	t := &T{3}
	v := reflect.ValueOf(t).Elem().Field(0)
	fmt.Println(v)             // 3
	fmt.Println(v.Interface()) // panic
}

قُدِّمت الطريقة reflect.Value.IsZero في Go 1.13. وتُستخدم للتحقق مما إذا كانت القيمة الأساسية لقيمة reflect.Value قيمة صفرية أم لا.

منذ Go 1.17، يمكن تحويل شريحة إلى مؤشر مصفوفة. ومع ذلك، قد يسبب هذا التحويل panic إذا كان طول نوع المصفوفة الأساسي للمؤشر كبيرًا جدًا. وتُستخدم الطريقة reflect.Value.CanConvert(T reflect.Type) المُقدَّمة في Go 1.17 للتحقق مما إذا كان التحويل سينجح أم لا.

مثال يستخدم الطريقة CanConvert:


package main

import (
	"fmt"
	"reflect"
)

func main() {
	s := reflect.ValueOf([]int{1, 2, 3, 4, 5})
	ts := s.Type()
	t1 := reflect.TypeOf(&[5]int{})
	t2 := reflect.TypeOf(&[6]int{})
	fmt.Println(ts.ConvertibleTo(t1)) // true
	fmt.Println(ts.ConvertibleTo(t2)) // true
	fmt.Println(s.CanConvert(t1))     // true
	fmt.Println(s.CanConvert(t2))     // false
}

هناك المزيد من الدوال والطرق المتعلقة بـ reflect.Value التي لم تُستخدم في الأمثلة أعلاه، يُرجى قراءة توثيق حزمة reflect لمعرفة استخداماتها. بالإضافة إلى ذلك، يُرجى ملاحظة أن هناك بعض التفاصيل المتعلقة بالانعكاس مذكورة في تفاصيل Go 101.