الأجناس العامة

الوضع الراهن للأجناس العامة المخصّصة في Go

شرحت الفصول السابقة المعرفة الأساسية عن الأجناس العامة المخصّصة في Go. وسيسرد هذا الفصل بعض الميزات الناقصة في التصميم والتطبيق الحاليين للأجناس العامة المخصّصة في Go.

تضمين وسائط النوع غير مسموح به حالياً

بسبب تعقيدات التصميم والتطبيق، حالياً (Go 1.25)، لا يُسمح بتضمين وسائط النوع في أنواع الواجهات ولا في أنواع البنى.

مثلاً، تعريف النوع التالي غير قانوني.

type Derived[Base any] struct {
	Base // خطأ

	x bool
}

يرجى الاطلاع على هذه المسألة لمعرفة الأسباب.

لا تُحسب مجموعة طرق القيد بالكامل في بعض الحالات

تنص مواصفة Go على ما يلي:

مجموعة طرق نوع الواجهة هي تقاطع مجموعات طرق كل نوع في مجموعة أنواع الواجهة.

لكن حالياً (سلسلة أدوات Go 1.25)، تُحسب في مجموعات الطرق فقط الطرق المحدّدة صراحةً في أنواع الواجهات. مثلاً، في الشيفرة التالية، ينبغي أن تحتوي مجموعة طرق القيد على كل من Foo وBar، وينبغي أن تُصرَّف الشيفرة دون مشاكل، لكنها لا تُصرَّف (اعتباراً من سلسلة أدوات Go 1.25).

package main

type S struct{}

func (S) Bar() {}

type C interface {
	S
	Foo()
}

func foobar[T C](v T) {
	v.Foo() // حسن
	v.Bar() // v.Bar غير معرّفة
}

func main() {}

من المقرر إزالة هذا القيد في إصدارات سلسلة أدوات Go المستقبلية.

لا توجد طرق لتحديد مجموعة حقول لقيد

نعلم أن نوع الواجهة قد يحدّد مجموعة طرق. لكن حتى الآن (Go 1.25)، لا يمكنه تحديد مجموعة حقول (بنية).

يوجد اقتراح بهذا الشأن: https://github.com/golang/go/issues/51259.

قد يُرفع هذا القيد في إصدارات Go المستقبلية.

لا يمكن الوصول إلى حقول قيم وسائط النوع

حالياً (Go 1.25)، حتى لو كانت جميع الأنواع في مجموعة أنواع قيد ما بنى وتشترك في بعض الحقول الشائعة، فلا تزال الحقول الشائعة غير قابلة للاستخدام.

مثلاً، تفشل جميع الدوال العامة في المثال التالي في التصريف.

package main

type S1 struct {
	X int
}

type S2 struct {
	X int `json:X`
}

type S3 struct {
	X int
	Z bool
}

type S4 struct {
	S1
}

func F12[T S1 | S2](v T) {
	_ = v.X // خطأ: v.x غير معرّفة
}

func F13[T S1 | S3](v T) {
	_ = v.X // خطأ: v.x غير معرّفة
}

func F14[T S1 | S4](v T) {
	_ = v.X // خطأ: v.x غير معرّفة
}

func main() {}

وفي حالة خاصة، لا تُصرَّف الشيفرة التالية أيضاً.

type S struct{x, y, z int}

func mod[T S](v T) {
	_ = v.x // خطأ: v.x غير معرّفة
}

يتمثل أحد الحلول المؤقتة (المطوّلة جداً) في تحديد/تعريف بعض طرق الجلب (getter) والتعيين (setter) للقيود والأنواع الملموسة المعنية.

أُضيف هذا القيد (الموصوف في القسم الحالي) قبل إصدار Go 1.18 بقليل. وقد يُزال ابتداءً من إصدار Go مستقبلي.

لا تُدعم كتل الشيفرة type-switch على قيم وسائط النوع حالياً

ذُكر أن وسيط النوع هو نوع واجهة من منظور دلالي. ومن ناحية أخرى، لوسيط النوع ازدواجية موجة-جسيم. فهو في بعض الحالات يتصرّف كالأنواع الموجودة في مجموعة أنواعه.

حتى الآن (Go 1.25)، لا يمكن تأكيد قيم وسائط النوع. وتفشل الدالتان التاليتان معاً في التصريف.

func tab[T any](x T) {
	if n, ok := x.(int); ok { // خطأ
		_ = n
	}
}

func kol[T any]() {
	var x T
	switch x.(type) { // خطأ
	case int:
	case []bool:
	default:
	}
}

تُصرَّف النسختان المعدَّلتان التاليتان من الدالتين أعلاه دون مشاكل:

func tab2[T any](x T) {
	if n, ok := any(x).(int); ok {
		_ = n
	}
}

func kol2[T any]() {
	var x T
	switch any(x).(type) {
	case int:
	case []bool:
	default:
	}
}

يوجد اقتراح لاستخدام كتل الشيفرة type-switch مباشرةً على وسائط النوع، هكذا:

func kol3[T any]() {
	switch T {
	case int:
	case []bool:
	default:
	}
}

يرجى متابعة هذه المسألة لمعرفة تقدّم هذه المشكلة.

لا تُدعم الطرق العامة

حالياً (Go 1.25)، وبسبب صعوبات التصميم والتطبيق، لا تُدعم الطرق العامة (وليست طرق الأنواع العامة).

مثلاً، الشيفرة التالية غير قانونية.

import "sync"

type Lock struct {
	mu sync.Mutex
}

func (l *Lock) Inc[T ~uint32 | ~uint64](x *T) {
	l.Lock()
	defer l.Unlock()
	*x++
}

كم عدد الطرق الملموسة التي يملكها النوع Lock؟ لا نهائي! لأنه توجد أنواع uint32 وuint64 لا نهائية. وهذا يجلب صعوبات كبيرة لجعل الحزمة القياسية reflect تحافظ على التوافق العكسي.

توجد مسألة بهذا الشأن.

لا توجد طرق لإنشاء قيد يسمح بإسنادات تتضمّن أنواعاً ذات أنواع كامنة غير محدّدة

ولا توجد قيود مُعلنة مسبقاً مثل القيدين المفترضين assignableTo وassignableFrom التاليين.

// هذه الدوال لا تُصرَّف.

func yex0[Tx assignableTo[Ty], Ty assignableFrom[Tx]](x Tx, y Ty) {
	y = x
}

func yex1[Tx any, Ty assignableFrom[Tx]](x Tx, y Ty) {
	y = x
}

func yex2[Tx assignableTo[Ty], Ty any](x Tx, y Ty) {
	y = x
}

لا توجد طرق لإنشاء قيد يسمح بتحويلات تتضمّن أنواعاً ذات أنواع كامنة غير محدّدة

ولا توجد قيود مُعلنة مسبقاً مثل القيدين المفترضين convertibleTo وconvertibleFrom التاليين.

// هذه الدالة لا تُصرَّف.
func x2y[Tx convertibleTo[Ty], Ty convertibleFrom[Tx],
		// معطى القيمة الثاني
		// لأغراض استنتاج النوع.
		](xs []Tx, _ Ty) []Ty {
	if xs == nil {
		return nil
	}
	ys := make([]Ty, len(xs))
	for i := range xs {
		ys[i] = Ty(xs[i])
	}
	return ys
}

var bs = []byte{61, 62, 63, 64, 65, 66}
var ss = x2y(bs, "")
var is = x2y(bs, 0)
var fs = x2y(bs, .0)

حالياً، يوجد تطبيق بديل غير أنيق:

func x2y[Tx any, Ty any](xs []Tx, f func(Tx) Ty) []Ty {
	if xs == nil {
		return nil
	}
	ys := make([]Ty, len(xs))
	for i := range xs {
		ys[i] = f(xs[i])
	}
	return ys
}

var bs = []byte{61, 62, 63, 64, 65, 66}
var ss = x2y(bs, func(x byte) string {
	return string(x)
})
var is = x2y(bs, func(x byte) int {
	return int(x)
})
var fs = x2y(bs, func(x byte) float64 {
	return float64(x)
})

يحتاج الحل البديل إلى دالة رد نداء، مما يجعل الشيفرة مطوّلة جداً وأقل كفاءة بكثير، مع أنني أعترف بأن له حالات استخدام أكثر.

لا توجد طرق لإنشاء قيد لا تحققه إلا أنواع الواجهات أو الأنواع غير الواجهية

لا توجد طرق.