يعني القيد قيد نوع، ويُستخدم لتقييد بعض وسائط الأنواع. يمكننا النظر إلى القيود على أنها أنواع للأنواع.
العلاقة بين القيد ووسيط النوع كالعلاقة بين النوع والقيمة. إذا قلنا إن الأنواع قوالب للقيم (والقيم نسخ من الأنواع)، فإن القيود قوالب للأنواع (والأنواع نسخ من القيود).
وسيط النوع هو نوع يُعرَّف في قائمة وسائط الأنواع ويمكن استخدامه في مواصفة نوع عام أو تعريف دالة/طريقة عامة. كل وسيط نوع هو نوع مسمّى متميز.
ستُشرح قوائم وسائط الأنواع بالتفصيل في قسم لاحق.
كما ذُكر في الفصل السابق، قيود الأنواع هي في الحقيقة أنواع واجهات. ولكي تصبح أنواع الواجهات قادرة على أداء دور القيد، عزّزت Go 1.18 تعبيرية أنواع الواجهات بدعم عدة صيغ جديدة.
صيغة الواجهات المعزّزة
أُدخلت بعض الصيغ الجديدة إلى Go لجعل استخدام أنواع الواجهات كقيود ممكناً.
- صيغة ~T، حيث T قيمة حرفية لنوع أو اسم نوع. يجب أن يدل T على نوع غير واجهة يكون نوعه الكامن هو نفسه (لذا لا يجوز أن يكون T وسيط نوع، وهو ما سيُشرح أدناه). تدل هذه الصيغة على مجموعة أنواع تضم كل الأنواع التي نوعها الكامن هو T . تُسمى صيغة ~T صيغة التلدة أو تلدة النوع في هذا الكتاب (وتُسمى في مواضع أخرى الحد الكامن ونوع التقريب).
- صيغة T1 | T2 | ... | Tn، وتُسمى اتحاد الحدود (أو اتحاد الأنواع/الحدود في هذا الكتاب). يجب أن يكون كل حد Tx صيغة تلدة، أو قيمة حرفية لنوع، أو اسم نوع، ولا يجوز أن يدل على وسيط نوع. هناك بعض المتطلبات لحدود الاتحاد. ستُوصف هذه المتطلبات في قسم أدناه.
لاحظ أن القيمة الحرفية للنوع تدل دائماً على نوع غير مسمّى، بينما قد يدل اسم النوع على نوع مسمّى أو نوع غير مسمّى.
بعض الأمثلة القانونية للصيغ الجديدة:
// صيغ التلدة
~int
~[]byte
~map[int]string
~chan struct{}
~struct{x int}
// اتحادات الحدود
uint8 | uint16 | uint32 | uint64
~[]byte | ~string
map[int]int | []int | [16]int | any
chan struct{} | ~struct{x int}
نعلم أنه قبل Go 1.18، كان يمكن لنوع الواجهة أن يضمّن
- عدداً اعتباطياً من مواصفات الطرق (عناصر الطرق، وهي أحد صنفي عناصر الواجهة)؛
- عدداً اعتباطياً من أسماء الأنواع (عناصر الأنواع، وهي الصنف الآخر من عناصر الواجهة)، لكن يجب أن تدل أسماء الأنواع على أنواع واجهات.
خفّفت Go 1.18 قيود عناصر الأنواع، بحيث يمكن الآن لنوع الواجهة أن يضمّن عناصر الأنواع التالية:
- أي قيم حرفية للأنواع أو أسماء أنواع، سواء دلت على أنواع واجهات أم لا، لكن يجب ألا تدل على وسائط أنواع.
- صيغ التلدة.
- اتحادات الحدود.
ترتيب عناصر الواجهة المضمَّنة في نوع واجهة ليس مهماً.
يعرض مقتطف الشيفرة التالي بعض تعريفات أنواع الواجهات، وفيها لا تكون قيم الواجهات الحرفية في تعريفَي N وO قانونية إلا بدءاً من Go 1.18.
type L interface {
Run() error
Stop()
}
type M interface {
L
Step() error
}
type N interface {
M
interface{ Resume() }
~map[int]bool
~[]byte | string
}
type O interface {
Pause()
N
string
int64 | ~chan int | any
}
تضمين نوع واجهة في آخر مكافئ لتوسيع عناصر الأول (توسيعاً متكرراً) في الثاني. في المثال أعلاه، تعريفات M وN وO مكافئة للتعريفات التالية:
type M interface {
Run() error
Stop()
Step() error
}
type N interface {
Run() error
Stop()
Step() error
Resume()
~map[int]bool
~[]byte | string
}
type O interface {
Run() error
Stop()
Step() error
Pause()
Resume()
~map[int]bool
~[]byte | string
string
int64 | ~chan int | any
}
يمكننا النظر إلى قيمة حرفية نوع أو اسم نوع أو صيغة تلدة منفردة على أنها اتحاد حدود بحد واحد فقط. ببساطة، منذ Go 1.18، يمكن لنوع الواجهة أن يحدد بعض الطرق ويضمّن بعض اتحادات الحدود.
يُسمى نوع الواجهة الخالي من أي عناصر مضمَّنة واجهة فارغة. مثلاً، يدل الاسم المستعار للنوع any المعرّف مسبقاً على نوع واجهة فارغة.
مجموعات الأنواع وتنفيذ الأنواع
قبل Go 1.18، كان نوع الواجهة يُعرَّف بأنه مجموعة طرق. منذ Go 1.18، يُعرَّف نوع الواجهة بأنه مجموعة أنواع. تتألف مجموعة الأنواع من أنواع غير واجهات فقط.
في الواقع، كل حد نوع يعرّف مجموعة طرق. تتبع حسابات مجموعات الأنواع القواعد التالية:
- مجموعة الأنواع لقيمة حرفية لنوع غير واجهة أو اسم نوع غير واجهة تحتوي فقط على النوع الذي تدل عليه القيمة الحرفية أو اسم النوع.
- كما ذُكر للتو أعلاه، مجموعة الأنواع لصيغة التلدة ~T هي مجموعة الأنواع التي أنواعها الكامنة هي T . نظرياً، هذه مجموعة لا نهائية.
- مجموعة الأنواع لمواصفة طريقة هي مجموعة الأنواع غير الواجهية التي تتضمن مجموعات طرقها مواصفة الطريقة. نظرياً، هذه مجموعة لا نهائية.
- مجموعة الأنواع لواجهة فارغة (مثل
anyالمعرّفة مسبقاً) هي مجموعة كل الأنواع غير الواجهية. نظرياً، هذه مجموعة لا نهائية. - مجموعة الأنواع لاتحاد الحدود T1 | T2 | ... | Tn هي اتحاد مجموعات الأنواع للحدود.
- مجموعة الأنواع لواجهة غير فارغة هي تقاطع مجموعات الأنواع لعناصر الواجهة.
وفق المواصفة الحالية، يكون قيدان غير مسمّيين متكافئين إذا تساوت مجموعتاهما من الأنواع.
بالنظر إلى الأنواع المعرّفة في مقتطف الشيفرة التالي، تُوصف مجموعة الأنواع لكل نوع واجهة في التعليق السابق لذلك النوع.
type Bytes []byte // النوع الكامن هو []byte
type Letters Bytes // النوع الكامن هو []byte
type Blank struct{}
type MyString string // النوع الكامن هو string
func (MyString) M() {}
func (Bytes) M() {}
func (Blank) M() {}
// مجموعة الأنواع لـ P تحتوي نوعاً واحداً فقط:
// []byte.
type P interface {[]byte}
// مجموعة الأنواع لـ Q تحتوي
// []byte وBytes وLetters.
type Q interface {~[]byte}
// مجموعة الأنواع لـ R تحتوي نوعين فقط:
// []byte وstring.
type R interface {[]byte | string}
// مجموعة الأنواع لـ S فارغة.
type S interface {R; M()}
// مجموعة الأنواع لـ T تحتوي:
// []byte وBytes وLetters وstring وMyString.
type T interface {~[]byte | ~string}
// مجموعة الأنواع لـ U تحتوي:
// MyString وBytes وBlank.
type U interface {M()}
// V <=> P
type V interface {[]byte; any}
// مجموعة الأنواع لـ W تحتوي:
// Bytes وMyString.
type W interface {T; U}
// Z <=> any. Z واجهة فارغة. مجموعتها
// من الأنواع تحتوي كل الأنواع غير الواجهية.
type Z interface {~[]byte | ~string | any}
يرجى ملاحظة أن عناصر الواجهة تُفصل بفاصلة منقوطة (;)، إما صراحةً أو ضمناً (يدرج المصرِّفون بعض الفواصل المنقوطة الناقصة حسب الحاجة أثناء التصريف). قيم الواجهات الحرفية التالية متكافئة، وكلها تدل على نوع واجهة غير مسمّى مجموعته من الأنواع فارغة. نوع الواجهة غير المسمّى هذا والنوع الكامن للنوع S المعروض في مقتطف الشيفرة أعلاه متماثلان فعلاً.
interface {~string; string; M();}
interface {~string; string; M()}
interface {
~string
string
M()
}
إذا كانت مجموعة الأنواع للنوع X مجموعة فرعية من نوع الواجهة Y، نقول إن X ينفّذ Y. هنا، قد يكون X نوع واجهة أو نوعاً غير واجهة.
لأن مجموعة الأنواع لنوع واجهة فارغة مجموعة شاملة لمجموعات الأنواع لأي أنواع، فإن كل الأنواع تنفّذ نوع واجهة فارغة. ومن جهة أخرى، لأن مجموعة الأنواع الفارغة مجموعة فرعية من أي مجموعات أنواع، فإن نوع واجهة مجموعته من الأنواع فارغة ينفّذ أي نوع واجهة.
خُذ الأنواع المعرّفة أعلاه مثالاً،
- نوع الواجهة S، الذي مجموعته من الأنواع فارغة، ينفّذ كل أنواع الواجهات.
- كل الأنواع تنفّذ نوع الواجهة Z، وهو في الواقع نوع واجهة فارغة.
تُسمى قائمة الطرق التي يحددها نوع واجهة مجموعة طرق نوع الواجهة. إذا كان نوع الواجهة X ينفّذ نوع واجهة آخر Y، فيجب أن تكون مجموعة طرق X مجموعة شاملة لـ Y.
تُسمى أنواع الواجهات التي يمكن تعريف مجموعات أنواعها كلياً بواسطة مجموعة طرق (قد تكون فارغة) أنواع الواجهات الأساسية. قبل 1.18، كانت Go تدعم أنواع الواجهات الأساسية فقط. يمكن استخدام الواجهات الأساسية كنوعي قيم أو كقيود أنواع، أما الواجهات غير الأساسية فلا يمكن استخدامها إلا كقيود أنواع (حتى Go 1.25).
خُذ الأنواع المعرّفة أعلاه مثالاً،، L وM وU وZ وany أنواع أساسية.
في الشيفرة التالية، يُصرَّف سطرا تعريف x وy بنجاح، لكن سطر تعريف z يفشل في التصريف.
var x any
var y interface {M()}
// خطأ: الواجهة تحتوي قيود أنواع
var z interface {~[]byte}
لم يتضح بعد ما إذا كانت أنواع الواجهات غير الأساسية ستُدعم كنوعي قيم في إصدارات Go المستقبلية أم لا.
لاحظ أنه قبل سلسلة أدوات Go 1.19، لم تكن الأسماء المستعارة لأنواع الواجهات غير الأساسية مدعومة. تعريفات الأسماء المستعارة التالية لا تكون قانونية إلا بدءاً من سلسلة أدوات Go 1.19.
type C[T any] interface{~int; M() T}
type C1 = C[bool]
type C2 = comparable
type C3 = interface {~[]byte | ~string}
المزيد عن القيد comparable المعرّف مسبقاً
كما ذُكر سابقاً، إلى جانب any، قدّمت Go 1.18 معرّفاً جديداً آخر معرّفاً مسبقاً هو comparable، ويدل على نوع واجهة تتألف مجموعته من الطرق من كل الأنواع (غير الواجهية) القابلة للمقارنة بصرامة (سيُشرح «القابل للمقارنة بصرامة» في القسم التالي).
يمكن تضمين نوع الواجهة comparable في أنواع واجهات أخرى لاستبعاد الأنواع غير القابلة للمقارنة بصرامة من مجموعاتها من الأنواع. مثلاً، مجموعة الأنواع للقيد C المعرّف أدناه تحتوي نوعاً واحداً فقط: string، لأن الأنواع الأخرى في الاتحاد إما غير قابلة للمقارنة (الأنواع الثلاثة الأولى) وإما غير قابلة للمقارنة بصرامة (النوع الأخير).
type C interface {
comparable
[]byte | func() | map[int]bool | string | [2]any
}
(ملاحظة: فشلت بعض الإصدارات الأقدم من سلسلتي أدوات Go 1.18 و1.19 في استبعاد [2]any من مجموعة الأنواع للقيد C. أُصلح الخطأ في إصدارات أحدث من سلسلتي أدوات Go 1.18 و1.19.)
حالياً (Go 1.25)، تُعالَج واجهة comparable كنوع واجهة غير أساسي. لذا لا يمكن الآن استخدامها إلا كقيد لوسائط الأنواع، وليس كنوع قيم. الشيفرة التالية غير قانونية:
var x comparable = 123
من الواضح أن مجموعة الأنواع لواجهة comparable مجموعة فرعية من مجموعة الأنواع لواجهة any، لذا ينفّذ comparable بلا شك واجهة any، والعكس غير صحيح.
القابل للمقارنة مقابل القابل للمقارنة بصرامة
نعلم أنه رغم أن أنواع الواجهات قابلة للمقارنة، فإن مقارنة قيمتين من نوع واجهة ستؤدي إلى panic في وقت التشغيل إذا كان النوعان الديناميكيان لقيمتي الواجهة متماثلين وكان النوع الديناميكي المتماثل غير قابل للمقارنة. وقد تؤدي مقارنة قيم من أنواع بنى أو مصفوفات تحتوي مكوّنات واجهات إلى panic أيضاً.
مثلاً، ستؤدي أي من المقارنات المعروضة في دالة main إلى panic في وقت التشغيل.
package main
var m any = map[int]string{}
var a = [2]any{1, func(){}}
var s = struct{x any}{x: m}
func main() {
_ = m == m
_ = a == a
_ = s == s
}
قُدّم مفهوم «القابل للمقارنة بصرامة» في Go 1.20. مقارنة قيم من نوع قابل للمقارنة بصرامة مضمونة بلا panic في وقت التشغيل. يكون النوع العادي قابلاً للمقارنة بصرامة إذا كان قابلاً للمقارنة ولم يكن نوع واجهة ولا مركّباً من أنواع واجهات.
أنواع القيم التالية غير قابلة للمقارنة بصرامة:
- أنواع الواجهات (الأساسية).
- أنواع البنى التي تحتوي حقولها واجهات.
- أنواع المصفوفات التي تحتوي عناصرها واجهات.
- وسائط الأنواع التي تحتوي مجموعتها من الأنواع نوعاً واحداً على الأقل من الحالتين أعلاه (البنى والمصفوفات).
في المثال أعلاه، الأنواع any و[2]any وstruct{x any} كلها قابلة للمقارنة لكنها غير قابلة للمقارنة بصرامة.
كما ذُكر في القسم الأخير، كل الأنواع في مجموعة الأنواع لواجهة comparable المعرّفة مسبقاً قابلة للمقارنة بصرامة.
تنفيذ النوع مقابل إرضاء النوع
قبل Go 1.20، كان المصطلحان يُستخدمان بالتبادل. بعبارة أخرى، كان الوصفان التاليان متكافئين قبل Go 1.20:
- النوع X ينفّذ نوع الواجهة Y .
- النوع X يُرضي نوع الواجهة Y .
قبل Go 1.20، كانا يعنيان معاً أن مجموعة الأنواع للنوع X مجموعة فرعية من نوع الواجهة Y. لذا، قبل Go 1.20، إذا كان النوع X ينفّذ نوع الواجهة Y، فيجب أن يُرضي Y أيضاً؛ والعكس صحيح.
منذ Go 1.20، بقي معنى تنفيذ النوع كما هو. لكن قد يُرضي نوع قيمة عادي X أحياناً نوع واجهة Y حتى إن لم ينفّذ Y. بعبارة أخرى، منذ Go 1.20، إذا كان نوع قيمة عادي X ينفّذ نوع الواجهة Y، فيجب أن يُرضي Y أيضاً؛ لكن العكس غير صحيح.
وبشكل محدد، منذ Go 1.20، إذا كان نوع قيمة عادي قابل للمقارنة X غير قابل للمقارنة بصرامة ولا ينفّذ قيد نوع Y، لكن يمكن كتابة النوع الكامن لـ Y بالصيغة interface{ comparable; E }، حيث E نوع واجهة أساسي والنوع العادي X ينفّذ E، فإن X يُرضي Y أيضاً.
مثلاً، من المؤكد أن النوع any لا ينفّذ قيد النوع comparable. لكن عندما يُستخدم كنوع قيمة عادي، فإنه يُرضي comparable. لأن النوع الكامن لـ comparable يمكن كتابته بالصيغة interface{ comparable; any } والنوع any ينفّذ any.
في الشيفرة التالية، يُرضي النوعان *A و*B (لكنهما لا ينفّذان) نوع الواجهة C. لأن C يمكن كتابته بالصيغة interface{ comparable; interface{ M() } } وكل من *A و*B ينفّذ interface{ M() }.
type A [2]any
func (a *A) M() {}
type B struct{
A
x any
}
type C interface {
comparable
M()
}
حتى Go 1.25، يُستخدم إرضاء الأنواع للتحقق مما إذا كان نوع قيمة عادي يمكن استخدامه كمعطى نوع في تجسيد نوع/دالة عامة أم لا. يرجى قراءة الفصل التالي للتفاصيل.
متطلبات إضافية لحدود الاتحاد
ذُكر أعلاه أن حد الاتحاد لا يجوز أن يكون وسيط نوع. هناك متطلبان آخران لحدود الاتحاد.
الأول متطلب خاص بالتنفيذ: لا يمكن لاتحاد حدود يضم أكثر من حد واحد أن يحتوي المعرّف comparable المعرّف مسبقاً أو واجهات لها طرق. مثلاً، اتحادا الحدود التاليان غير قانونيين معاً (حتى سلسلة أدوات Go 1.25):
[]byte | comparable
string | error
لتبسيط الأوصاف، سينظر هذا الكتاب إلى نوع الواجهة comparable المعرّف مسبقاً على أنه نوع واجهة له طريقة (لكنه لن يعتبره نوع واجهة أساسي).
المتطلب (القيد) الآخر هو أن مجموعات الأنواع لكل حدود الأنواع غير الواجهية في اتحاد الحدود يجب ألا تتقاطع. مثلاً، في مقتطف الشيفرة التالي، يفشل اتحادا الحدود في التعريف الأول في التصريف، لكن الأخيرين يُصرَّفان بنجاح.
type _ interface {
int | ~int // خطأ
}
type _ interface {
interface{int} | interface{~int} // حسن
}
type _ interface {
int | interface{~int} // حسن
}
اتحادات الحدود الثلاثة في مقتطف الشيفرة أعلاه متكافئة منطقياً، ما يعني أن هذا القيد ليس معقولاً جداً. لذا قد يُزال في إصدارات Go اللاحقة، أو يصبح أكثر صرامة لإبطال التحايل.
قوائم وسائط الأنواع
من الأمثلة المعروضة في الفصل الأخير، نعلم أن قوائم وسائط الأنواع تُستخدم في مواصفات الأنواع العامة، وتعريفات الطرق لأنواع أساسية عامة، وتعريفات الدوال العامة.
تحتوي قائمة وسائط الأنواع على تعريف واحد على الأقل لوسيط نوع وتُحاط بأقواس مربعة. يتألف كل تعريف وسيط من جزء اسم وجزء قيد (يمكننا اعتبار القيود ضمنية في تعريفات الطرق لأنواع أساسية عامة). يمثل الاسم وسيط نوع مقيّداً بالقيد. تُفصل تعريفات الوسائط بفواصل في قائمة وسائط الأنواع.
في قائمة وسائط الأنواع، يجب أن تكون أسماء كل وسائط الأنواع موجودة. وقد تكون المعرّف الفارغ _ (ويُسمى الاسم الفارغ). يجب أن تكون كل الأسماء غير الفارغة في قائمة وسائط الأنواع فريدة.
على غرار قوائم وسائط القيم، إذا تطابقت قيود بعض تعريفات وسائط الأنواع المتتالية في قائمة وسائط الأنواع، فيمكن لتعريفات وسائط الأنواع هذه أن تتشارك جزء قيد مشتركاً في القائمة. مثلاً، قائمتا وسائط الأنواع التاليتان متكافئتان.
[A any, B any, X comparable, _ comparable]
[A, B any, X, _ comparable]
على غرار قوائم وسائط القيم، إذا كان الرمز ] الأيمن في قائمة وسائط الأنواع وآخر قيد في القائمة على السطر نفسه، فيسمح بإدراج فاصلة اختيارية بينهما. الفاصلة مطلوبة إذا لم يكونا على السطر نفسه.
مثلاً، في الشيفرة التالية، الأسطر في البداية قانونية والأسطر في النهاية غير قانونية.
// قانونية:
[T interface{~map[int]string}]
[T interface{~map[int]string},]
[T interface{~map[int]string},
]
[A, B any, _, _ comparable]
[A, B any, _, _ comparable,]
[A, B any, _, _ comparable,
]
[A, B any,
_, _ comparable]
// غير قانونية:
[A, B any, _, _ comparable
]
[T interface{~map[int]string}
]
وسائط الأنواع المتغيرة غير مدعومة.
لتبسيط الأوصاف، تُسمى مجموعة الأنواع الخاصة بقيد وسيط النوع أيضاً مجموعة الأنواع لوسيط النوع ومجموعة الأنواع لقيمة وسيط النوع في هذا الكتاب.
صيغة القيد المبسّطة
في قائمة وسائط الأنواع، إذا كان القيد يحتوي عنصراً واحداً فقط وكان ذلك العنصر عنصر نوع، فيمكن حذف interface{} المحيطة به للتسهيل. مثلاً، قائمتا وسائط الأنواع التاليتان متكافئتان.
[X interface{string|[]byte}, Y interface{~int}]
[X string|[]byte, Y ~int]
تجعل صيغ القيد المبسّطة الشيفرة تبدو أنظف بكثير. وفي معظم الحالات، لا تسبب أي مشكلات. لكنها قد تسبب التباسات في التحليل النحوي في بعض الحالات الخاصة. وعلى وجه الخصوص، قد تنشأ التباسات في التحليل النحوي عندما تعلن قائمة وسائط الأنواع في مواصفة نوع عام عن وسيط نوع واحد يكون قيده بصيغة مبسّطة ويبدأ بـ * أو (.
مثلاً، هل تعرّف الشيفرة التالية نوعاً عاماً؟
type G[T *int] struct{}
يعتمد ذلك على ما يدل عليه المعرّف int. إذا كان يدل على نوع (وهو محتمل جداً، لكن ليس مؤكداً)، فينبغي للمصرِّفين اعتبار الشيفرة تعرّف نوعاً عاماً. وإذا كان يدل على ثابت (وهو محتمل)، فسيعالج المصرِّفون T *int كتعبير ضرب ويعتبرون الشيفرة تعرّف نوع مصفوفة عادياً.
من الممكن للمصرِّفين التمييز بين ما يدل عليه المعرّف int، لكن تحقيق ذلك له بعض التكاليف. ولتجنب التكاليف، يعالج المصرِّفون المعرّف int دائماً كتعبير قيمة ويعتبرون التعريف أعلاه تعريف نوع مصفوفة عادي. لذا سيفشل سطر التعريف أعلاه في التصريف إذا لم يكن T أو int يدلان على ثوابت صحيحة.
إذاً كيف تعرّف نوعاً عاماً بوسيط نوع واحد يكون *int قيده؟ هناك طريقتان لتحقيق ذلك:
- استخدام صيغة القيد الكاملة، أو
- إتباع صيغة القيد المبسّطة بفاصلة.
تُعرض الطريقتان في مقتطف الشيفرة التالي:
// افترض أن int نوع معرّف مسبقاً.
type G[T interface{*int}] struct{}
type G[T *int,] struct{}
الطريقتان المعروضتان أعلاه مفيدتان أيضاً لبعض الحالات الخاصة الأخرى التي قد تسبب التباسات في التحليل النحوي أيضاً. مثلاً،
// قد تكون PA متغير مؤشر مصفوفة، أو اسم نوع.
// لا يعالجها المصرِّفون كاسم نوع.
type K[cap (*PA)] struct{}
// قد تكون S ثابت نصي، أو اسم نوع.
// لا يعالجها المصرِّفون كاسم نوع.
type L[len (S)] struct{}
فيما يلي حالة أخرى قد تسبب التباساً في التحليل النحوي.
// قد تكون T وint وbool ثلاثة ثوابت صحيحة،
// أو يكون int وbool نوعين معرّفين مسبقاً.
type C5[T *int|bool] struct{}
ينبغي إدراج فاصلة بعد القيد المفترض *int|bool لإزالة الالتباس.
type C5[T *int|bool, ] struct{} // يُصرَّف بنجاح
(ملاحظة: لا تعمل هذه الطريقة مع سلسلة أدوات Go 1.18. كان خطأً وأُصلح منذ سلسلة أدوات Go 1.19.)
يمكننا أيضاً استخدام صيغة القيد الكاملة أو تبديل موضعي *int وbool ليجري التصريف بنجاح.
// افترض أن int وbool نوعان معرّفان مسبقاً.
type C5[T interface{*int|bool}] struct{}
type C5[T bool|*int] struct{}
ومن جهة أخرى، تعريفا النوعين العامين الغريبان التاليان قانونيان معاً.
// "make" وسيط نوع معرّف.
// قيده interface{chan int}.
type PtrToChan[make (chan int)] *make
// "new" وسيط نوع معرّف.
// قيده interface{[3]float64}.
type Matrix33[new ([3]float64)] [3]new
التعريفان ممارستان سيئتان حقاً. لا تستخدمهما في شيفرة جادة.
كل وسيط نوع هو نوع مسمّى متميز
منذ Go 1.18، تشمل الأنواع المسمّاة
- الأنواع المعرّفة مسبقاً، مثل int وbool وstring .
- الأنواع المعرّفة غير العامة.
- الأنواع المجسّدة للأنواع العامة.
- أنواع وسائط الأنواع (الأنواع المعرّفة في قوائم وسائط الأنواع).
وسيطا نوع مختلفان لا يكونان متماثلين أبداً.
نوع وسيط النوع قيد، أي نوع واجهة. هذا يعني أن النوع الكامن لنوع وسيط النوع ينبغي أن يكون نوع واجهة. لكن هذا لا يعني أن وسيط النوع يتصرف كنوع واجهة. فلا يمكن لقيمه أن تغلّف قيماً غير واجهية ولا أن يُجرى عليها تأكيد نوع (حتى Go 1.25). في الواقع، الحديث عن الأنواع الكامنة لوسائط الأنواع لا معنى له تقريباً. نحتاج فقط إلى معرفة أن النوع الكامن لوسيط النوع ليس نفسه. وينبغي أن نعتبر أن وسيطَي نوع لا يتشاركان أبداً نوعاً كامناً متماثلاً، حتى لو تطابق قيدا وسيطَي النوع.
في الواقع، وسيط النوع مجرد عنصر نائب للأنواع في مجموعته من الأنواع. وبشكل عام، يمثل نوعاً يمتلك السمات المشتركة للأنواع في مجموعته من الأنواع.
بما أن النوع الكامن لنوع وسيط النوع ليس نوع وسيط النوع نفسه، فإن صيغة التلدة ~T غير قانونية إذا كان T وسيط نوع. لذا فإن قائمتي وسائط الأنواع التاليتين (المتكافئتين) غير قانونيتين.
[A int, B ~A] // خطأ
[A interface{int}, B interface{~A}] // خطأ
كما ذُكر أعلاه، لا يُسمح أيضاً بتضمين وسائط الأنواع كأسماء أنواع وحدود أنواع في نوع واجهة. التعريفان التاليان غير قانونيين أيضاً.
type Cx[T int] interface {
T
}
type Cy[T int] interface {
T | []string
}
في الواقع، حالياً (Go 1.25)، لا يجوز تضمين وسائط الأنواع في أنواع البنى أيضاً.
نطاقات وسائط الأنواع
تقول مواصفة Go:
- يبدأ نطاق معرّف يدل على وسيط نوع لدالة أو معرّف بواسطة مستقبِل طريقة بعد اسم الدالة وينتهي عند نهاية جسم الدالة.
- يبدأ نطاق معرّف يدل على وسيط نوع لنوع بعد اسم النوع وينتهي عند نهاية مواصفة النوع.
لذا فإن تعريف النوع التالي صالح، حتى لو كان استخدام وسيط النوع E سابقاً لتعريفه. يُستخدم وسيط النوع E في قيد وسيط النوع S،
type G[S ~[]E, E int] struct{}
يرجى ملاحظة ما يلي:
- كما ذُكر في القسم الأخير، رغم أن E نوع وسيط نوع، فإن []E نوع (شريحة) عادي.
- النوع الكامن لـ S هو interface{~[]E} ، وليس []E .
- النوع الكامن لـ E هو interface{int} ، وليس int .
وفق مواصفة Go، تفشل تعريفات الدوال والطرق في الشيفرة التالية كلها في التصريف.
type C any
func foo1[C C]() {} // خطأ: لا يمكن استخدام وسيط نوع كقيد
func foo2[T C](T T) {} // خطأ: T معرّف مرة أخرى
type G[G any] struct{x G} // حسن
func (E G[E]) Bar1() {} // خطأ: E معرّف مرة أخرى
ينبغي أن يُصرَّف تعريف الطريقة Bar2 التالي بنجاح، لكن مع سلسلة أدوات Go v1.24+ فقط.
type G[G any] struct{x G}
func (v G[G]) Bar2() {}
قيم الأنواع الحرفية المركّبة (الأنواع غير المسمّاة) التي تحتوي وسائط أنواع تدل على أنواع عادية
مثلاً، *T دائماً نوع (مؤشر) عادي. إنه قيمة حرفية لنوع، لذا نوعه الكامن هو نفسه، سواء كان T وسيط نوع أم لا. قائمة وسائط الأنواع التالية قانونية.
[A int, B *A] // حسن
وللسبب نفسه، قائمتا وسائط الأنواع التاليتان قانونيتان أيضاً.
[T ~string|~int, A ~[2]T, B ~chan T] // حسن
[T comparable, M ~map[T]int32, F ~func(T) bool] // حسن
المزيد عن تعريفات الأنواع والدوال العامة
رأينا في الفصل الأخير تعريف نوع عام وبعض تعريفات الدوال العامة. وخلافاً لتعريفات الأنواع والدوال العادية، لكل تعريف من التعريفات العامة جزء قائمة وسائط أنواع.
لا يخطط هذا الكتاب للحديث أكثر عن صيغة تعريف الأنواع والدوال العامة.
يجب أن يكون جزء نوع المصدر في تعريف النوع العام نوعاً عادياً. لذا قد يكون
- قيمة حرفية لنوع مركّب. كما ذُكر أعلاه، تمثل القيمة الحرفية للنوع المركّب دائماً نوعاً عادياً.
- اسم نوع يدل على نوع عادي.
- نوع مجسّد. ستُشرح تجسيدات الأنواع بالتفصيل في الفصل التالي. في الوقت الحالي، نحتاج فقط إلى معرفة أن كل نوع مجسّد نوع عادي مسمّى.
تعرض الشيفرة التالية بعض تعريفات الأنواع العامة بمختلف أنواع المصادر. كل هذه التعريفات صالحة.
// أنواع المصادر أسماء أنواع عادية.
type (
Fake1[T any] int
Fake2[_ any] []bool
)
// نوع المصدر نوع غير مسمّى (نوع مركّب).
type MyData [A any, B ~bool, C comparable] struct {
x A
y B
z C
}
// نوع المصدر نوع مجسّد.
type YourData[C comparable] MyData[string, bool, C]
لا يجوز استخدام وسائط الأنواع كأنواع مصادر في تعريفات الأنواع العامة. مثلاً، الشيفرة التالية لا تُصرَّف.
type G[T any] T // خطأ