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
}
من المثال أعلاه، يمكننا أن نجد ما يلي:
- بالنسبة للأنواع غير الواجهية، لا تعيد
reflect.Type.NumMethodسوى عدد الطرق المُصدَّرة (بما فيها المعلنة ضمنيًا) لنوع ما. ولا يمكننا الحصول على معلومات طريقة غير مُصدَّرة باستخدام الطريقةreflect.Type.MethodByName. أما بالنسبة لأنواع الواجهات فلا توجد هذه القيود (لم تُذكر هذه الحقيقة في توثيق الطريقتين قبل Go 1.16). وينطبق هذا الوضع أيضًا على الطرق المقابلة لنوعreflect.Valueالمُقدَّم في القسم التالي. - على الرغم من أن استدعاء الطريقة
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 أخرى.
- إحدى الطريقتين هي استدعاء الطريقة
Elemلقيمةreflect.Valueتمثّل قيمة المؤشر. - والطريقة الأخرى هي تمرير قيمة
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.