ستقدّم المقالات التالية للمقال الحالي مزيداً من أنواع Go. لفهم تلك المقالات بسهولة وعمق، من الأفضل أن تقرأ المحتويات التالية في هذا المقال أولاً قبل قراءة تلك المقالات.
فئتان من أنواع Go
يمكن النظر إلى Go كلغة من عائلة C، وهذا ما تؤكّده المقالتان السابقتان المؤشرات في Go والبنى في Go. فبنى الذاكرة لأنواع البنى وأنواع المؤشرات في Go وC متشابهة كثيراً.
من ناحية أخرى، يمكن أيضاً النظر إلى Go كإطار عمل للغة C. ويتجلّى هذا أساساً في أن Go تدعم عدة أنواع لا تكون بنى ذاكرة قيمها شفافة تماماً، بينما السمة الرئيسية لأنواع C هي أن بنى ذاكرة قيم C شفافة. فكل قيمة C في الذاكرة تشغل كتلة ذاكرة واحدة (مقطع ذاكرة متصل واحد). لكن قيمة من بعض أنواع Go قد تُستضاف غالباً على أكثر من كتلة ذاكرة واحدة.
في ما يلي، نسمّي الأجزاء (الموزّعة على كتل ذاكرة مختلفة) المكوّنة لقيمة ما أجزاء القيمة (value parts). والقيمة التي تُستضاف على أكثر من كتلة ذاكرة تتكوّن من جزء قيمة مباشر واحد وعدة أجزاء كامنة غير مباشرة يشير إليها ذلك الجزء المباشر.
تصف الفقرات أعلاه فئتين من أنواع Go:
| أنواع تُستضاف قيمها كلٌّ على كتلة ذاكرة واحدة فقط | أنواع قد تُستضاف قيمها كلٌّ على عدة كتل ذاكرة |
|---|---|
![]() |
![]() |
| الأنواع المنطقية | |
| الأنواع العددية | |
| أنواع المؤشرات | |
| أنواع المؤشرات غير الآمنة | |
| أنواع البنى | |
| أنواع المصفوفات | أنواع الشرائح |
| أنواع الخرائط | |
| أنواع القنوات | |
| أنواع الدوال | |
| أنواع الواجهات | |
| أنواع النصوص |
ستقدّم مقالات Go 101 التالية شروحاً مفصّلة لكثير من أنواع الأنواع المدرجة في الجدول أعلاه. أما هذا المقال فليس إلا تمهيداً لفهم تلك الشروح بسهولة أكبر.
لاحظ،
- مسألة احتواء قيم الواجهات والنصوص على أجزاء كامنة تعتمد على المصرِّف. فبالنسبة إلى تطبيق المصرِّف القياسي لـ Go، قد تحتوي قيم الواجهات والنصوص على أجزاء كامنة.
- من الصعب، بل من المستحيل، إثبات ما إذا كانت قيم الدوال قد تحتوي على أجزاء كامنة. في Go 101، سنعتبر أن قيم الدوال قد تحتوي على أجزاء كامنة.
توفّر أنواع الفئة الثانية راحة كبيرة في برمجة Go بتغليفها كثيراً من تفاصيل التنفيذ. وقد تعتمد مصرِّفات Go المختلفة تطبيقات داخلية مختلفة لهذه الأنواع، لكن السلوكيات الخارجية لقيم هذه الأنواع يجب أن تحقق المتطلبات المحددة في مواصفات Go.
أنواع الفئة الثانية ليست أنواعاً جوهرية جداً بالنسبة إلى لغة ما، إذ يمكننا تنفيذها من الصفر باستخدام أنواع الفئة الأولى. لكن بتغليف بعض الوظائف الشائعة أو الفريدة ودعم هذه الأنواع كمواطنين من الدرجة الأولى في Go، تصبح تجربة برمجة Go ممتعة ومثمرة.
من ناحية أخرى، تخفي عمليات التغليف المعتمدة في تنفيذ أنواع الفئة الثانية كثيراً من التعريفات الداخلية لهذه الأنواع. وهذا يمنع مبرمجي Go من رؤية الصورة الكاملة لهذه الأنواع، ويضع أحياناً بعض العقبات أمام فهم Go فهماً أفضل.
لمساعدة مبرمجي Go (gophers) على فهم أنواع الفئة الثانية وقيمها فهماً أفضل، ستعرض المحتويات التالية من هذا المقال تعريفات البنية الداخلية لهذه الأنواع. ولن تُشرح هنا تطبيقات هذه الأنواع بالتفصيل. والشروح الواردة في هذا المقال مبنية على التطبيقات التي يستخدمها المصرِّف القياسي لـ Go، لكنها ليست مطابقة لها تماماً.
نوعان من أنواع المؤشرات في Go
قبل عرض تعريفات البنية الداخلية لأنواع الفئة الثانية، لنوضّح أكثر مسألة المؤشرات والإشارات.
لقد تعلّمنا مؤشرات Go في المقال قبل السابق. وأنواع المؤشرات في ذلك المقال هي أنواع مؤشرات آمنة من حيث النوع. في الواقع، تدعم Go أيضاً أنواع مؤشرات غير آمنة من حيث النوع. ونوع unsafe.Pointer الموفَّر في الحزمة القياسية unsafe يشبه void* في لغة C.
في معظم مقالات Go 101 الأخرى، ما لم يُذكر خلاف ذلك صراحةً، فإن ذكر نوع مؤشر يعني نوع مؤشر آمن من حيث النوع. لكن في الأجزاء التالية من هذا المقال، قد يعني ذكر مؤشر مؤشراً آمناً من حيث النوع أو مؤشراً غير آمن من حيث النوع.
تخزّن قيمة المؤشر عنوان ذاكرة قيمة أخرى، ما لم تكن قيمة المؤشر مؤشراً nil. ويمكننا القول إن قيمة المؤشر تشير إلى القيمة الأخرى، أو إن القيمة الأخرى مُشار إليها بواسطة قيمة المؤشر. ويمكن أيضاً الإشارة إلى القيم إشارة غير مباشرة.
- إذا كانت لقيمة بنية
aحقل مؤشرbيشير إلى قيمةc، فيمكننا القول إن قيمة البنيةaتشير أيضاً إلى القيمةc. - إذا كانت قيمة
xتشير (مباشرةً أو غير مباشرة) إلى قيمةy، وكانت القيمةyتشير (مباشرةً أو غير مباشرة) إلى قيمةz، فيمكننا أيضاً القول إن القيمةxتشير (إشارة غير مباشرة) إلى القيمةz.
في ما يلي، نسمّي نوع البنية الذي يحتوي على حقول من أنواع مؤشرات نوعاً مُغلِّفاً للمؤشر (pointer wrapper type)، ونسمّي النوع الذي قد تحتوي قيمه على مؤشرات (مباشرةً أو غير مباشرة) نوعاً حاملاً للمؤشر (pointer holder type). وأنواع المؤشرات وأنواع مُغلِّفات المؤشر كلها أنواع حاملة للمؤشر. كما أن أنواع المصفوفات التي تكون أنواع عناصرها حاملة للمؤشر هي أيضاً أنواع حاملة للمؤشر. (ستُشرح أنواع المصفوفات في المقال التالي.)
تعريفات داخلية (محتملة) لأنواع الفئة الثانية
لفهم سلوكيات وقت التشغيل لقيم الفئة الثانية فهماً أفضل، ليست فكرة سيئة أن نتصوّر أن هذه الأنواع معرَّفة داخلياً كأنواع من الفئة الأولى، كما هو موضح أدناه. وإذا لم تكن قد استخدمت جميع أنواع Go استخداماً مكثفاً، فلا حاجة الآن إلى محاولة استيعاب هذه التعريفات بوضوح. بدلاً من ذلك، يكفي أن تأخذ انطباعاً تقريبياً عنها وأن تعيد قراءة هذا المقال عندما تكتسب خبرة أكبر في برمجة Go لاحقاً. فمعرفة التعريفات معرفة تقريبية كافية لمساعدة مبرمجي Go على فهم الأنواع المشروحة في المقالات التالية.
التعريفات الداخلية لأنواع الخرائط والقنوات والدوال
التعريفات الداخلية لأنواع الخرائط والقنوات والدوال متشابهة:
// أنواع الخرائط
type _map *hashtableImpl
// أنواع القنوات
type _channel *channelImpl
// أنواع الدوال
type _function *functionImpl
إذن، داخلياً، أنواع هذه الأنواع الثلاثة ليست سوى أنواع مؤشرات. وبعبارة أخرى، الأجزاء المباشرة لقيم هذه الأنواع هي مؤشرات داخلياً. ولكل قيمة غير صفرية من هذه الأنواع، يشير جزءها المباشر (مؤشر) إلى جزء التنفيذ الكامن غير المباشر الخاص بها.
بالمناسبة، يستخدم المصرِّف القياسي لـ Go جداول تجزئة (hashtables) لتنفيذ الخرائط.
التعريف الداخلي لأنواع الشرائح
التعريف الداخلي لأنواع الشرائح كالتالي:
type _slice struct {
// يشير إلى العناصر الكامنة
elements unsafe.Pointer
// عدد العناصر والسعة
len, cap int
}
إذن، داخلياً، أنواع الشرائح هي أنواع بنى مُغلِّفة للمؤشر. ولكل قيمة شريحة غير صفرية جزء كامن غير مباشر يخزّن قيم عناصر قيمة الشريحة. ويشير الحقل elements في الجزء المباشر إلى الجزء الكامن غير المباشر لقيمة الشريحة.
التعريف الداخلي لأنواع النصوص
في ما يلي التعريف الداخلي لأنواع النصوص:
type _string struct {
elements *byte // يشير إلى البايتات الكامنة
len int // عدد البايتات
}
إذن، أنواع النصوص هي أيضاً أنواع بنى مُغلِّفة للمؤشر داخلياً. ولكل قيمة نص جزء كامن غير مباشر يخزّن بايتات قيمة النص، ويشير إلى الجزء غير المباشر الحقل elements في قيمة النص تلك.
التعريف الداخلي لأنواع الواجهات
في ما يلي التعريف الداخلي لأنواع الواجهات العامة:
type _interface struct {
dynamicType *_type // النوع الديناميكي
dynamicValue unsafe.Pointer // القيمة الديناميكية
}
داخلياً، أنواع الواجهات هي أيضاً أنواع بنى مُغلِّفة للمؤشر. ويحتوي التعريف الداخلي لنوع واجهة على حقلَي مؤشر. ولكل قيمة واجهة غير صفرية جزءان كامنان غير مباشرين يخزّنان النوع الديناميكي والقيمة الديناميكية لقيمة الواجهة تلك. ويشير إلى الجزأين غير المباشرين الحقلان dynamicType وdynamicValue في قيمة الواجهة تلك.
في الواقع، بالنسبة إلى المصرِّف القياسي لـ Go، لا يُستخدم التعريف أعلاه إلا لأنواع الواجهات الفارغة. وأنواع الواجهات الفارغة هي أنواع الواجهات التي لا تحدد أي طرق. ويمكننا تعلّم المزيد عن الواجهات في مقال الواجهات في Go لاحقاً. أما أنواع الواجهات غير الفارغة، فيُستخدم لها تعريف كالتعريف التالي.
type _interface struct {
dynamicTypeInfo *struct {
dynamicType *_type // النوع الديناميكي
methods []*_function // جدول الطرق
}
dynamicValue unsafe.Pointer // القيمة الديناميكية
}
يخزّن الحقل methods في الحقل dynamicTypeInfo لقيمة واجهة الطرق المنفَّذة للنوع الديناميكي لقيمة الواجهة بالنسبة إلى نوع (الواجهة) الخاص بقيمة الواجهة.
لا تُنسخ أجزاء القيمة الكامنة في إسنادات القيم
تعلّمنا الآن أن التعريفات الداخلية لأنواع الفئة الثانية هي أنواع حاملة للمؤشر (مؤشر أو مُغلِّف مؤشر). ومعرفة ذلك مفيدة جداً لفهم سلوكيات نسخ القيم في Go.
في Go، كل إسناد قيمة (بما في ذلك تمرير الوسائط، وما إلى ذلك) هو نسخ سطحي للقيمة إذا كان نوعا القيمة الهدف والمصدر متطابقين (وإذا اختلف نوعاهما، فيمكننا اعتبار أن قيمة المصدر ستُحوَّل ضمنياً إلى نوع الهدف قبل إجراء ذلك الإسناد). وبعبارة أخرى، لا يُنسخ في إسناد القيمة إلا الجزء المباشر من قيمة المصدر إلى القيمة الهدف. وإذا كانت لقيمة المصدر أجزاء قيمة كامنة، فإن الجزأين المباشرين لقيمتي الهدف والمصدر سيشيران إلى الأجزاء الكامنة نفسها، أي أن قيمتي الهدف والمصدر ستتشاركان أجزاء القيمة الكامنة نفسها.

في الواقع، الأوصاف أعلاه ليست صحيحة 100% من الناحية النظرية، بالنسبة إلى النصوص والواجهات. تقول الأسئلة الشائعة الرسمية لـ Go إنه ينبغي نسخ جزء القيمة الديناميكية الكامن لقيمة واجهة أيضاً عند نسخ قيمة الواجهة. لكن بما أن القيمة الديناميكية لقيمة واجهة للقراءة فقط، فإن المصرِّف/وقت التشغيل القياسي لـ Go لا ينسخ أجزاء القيمة الديناميكية الكامنة عند نسخ قيم الواجهات. ويمكن اعتبار هذا تحسيناً من المصرِّف. والوضع نفسه ينطبق على قيم النصوص، ويُجرى التحسين نفسه (من المصرِّف/وقت التشغيل القياسي لـ Go) عند نسخ قيم النصوص. لذا، بالنسبة إلى المصرِّف/وقت التشغيل القياسي لـ Go، فإن الأوصاف في القسم السابق صحيحة 100% لقيم أي نوع.
وبما أن الجزء الكامن غير المباشر قد لا ينتمي إلى أي قيمة انتماءً حصرياً، فإنه لا يسهم في الحجم الذي تُرجعه الدالة unsafe.Sizeof.
حول مصطلحَي «Reference Type» و«Reference Value»
كلمة reference في عالم Go في فوضى كبيرة. فهي تجلب كثيراً من الالتباسات إلى مجتمع Go. فبعض المقالات، بما فيها بعض المقالات الرسمية، تستخدم reference كصفة للأنواع والقيم، أو تعامل reference على أنها نقيض value. وهذا مرفوض بشدة في Go 101. ولا أريد حقاً أن أجادل في هذه النقطة. سأكتفي هنا بذكر بعض الاستخدامات الخاطئة قطعاً لكلمة reference:
- أنواع الشرائح والخرائط والقنوات والدوال فقط هي أنواع مراجع في Go. (إذا كنا نحتاج فعلاً إلى مصطلح نوع المرجع في Go، فلا ينبغي أن نستثني أي نوع حامل للمؤشر من أنواع المراجع).
- المراجع نقيض القيم. (إذا كنا نحتاج فعلاً إلى مصطلح قيمة المرجع في Go، فالرجاء اعتبار قيم المراجع قيماً خاصة، لا نقيضاً للقيم.)
- بعض الوسائط تُمرَّر بالمرجع. (آسف، جميع الوسائط تُمرَّر بالنسخ، للأجزاء المباشرة، في Go.)
لا أعني أن مصطلحي نوع المرجع أو قيمة المرجع عديمَا الفائدة تماماً بالنسبة إلى Go، لكنني أرى أنهما ليسا جوهريين كثيراً، وأنهما يجلبان كثيراً من الالتباسات في استخدام Go. وإذا كنا نحتاج فعلاً إلى هذين المصطلحين، فأفضّل تعريفهما بأنهما «حاملان للمؤشر». ورأيي الشخصي أن الأفضل هو حصر كلمة reference في تمثيل العلاقات بين القيم فقط باستخدامها فعلاً أو اسماً، وعدم استخدامها صفةً أبداً. وهذا سيتجنّب كثيراً من الالتباسات في تعلّم Go وتعليمه واستخدامه.

