مدونة

من المؤسف أن عناصر البايت في نصوص Go الثابتة ليست ثوابت. ومن حسن الحظ ذلك

هل يُصرَّف برنامج Go التالي دون مشاكل؟


package main

const S = "Go"

const G = S[0]

func main() {}

قد يظن كثير من مبرمجي Go أنه ينبغي أن يُصرَّف دون مشاكل. لكنه يفشل في التصريف. لأن قيم عناصر النص غير ثابتة دائماً في Go، حتى لو كان النص ثابتاً.

من المؤسف أن Go 1.0 لم ينص على أن عناصر النصوص الثابتة هي أيضاً ثوابت.

وبسبب التوافق العكسي، يصعب تدارك هذا الأمر المؤسف. على سبيل المثال، يُصرَّف البرنامج التالي حالياً دون مشاكل. وهو يطبع 185 (-71 + 256).


package main

const S = "Go" // S[0] == 71

func main() {
	println(-S[0])
}

لكن إذا أصبحت عناصر النصوص الثابتة ثوابت، فلن يُصرَّف البرنامج، لأن نوع -S[0] هو byte (أي uint8)، بينما -71 يتجاوز نطاق byte.

إنه أمر مؤسف، وهو أيضاً من حسن الحظ.

إذا كانت عناصر النصوص الثابتة ثوابت، فستنشأ التباسات في التحليل في عصر الأجناس العامة المخصصة في Go. منذ Go 1.18، سيُعالَج السطر التالي كتعريف نوع عام، حيث S وسيط نوع و[4]*int قيده.


type T [S [4]*int] struct{}

ومع ذلك، إذا كانت عناصر النصوص الثابتة ثوابت، فقد يُعالَج S [4] كقيمة byte ثابتة إذا كان S نصاً ثابتاً. وفي مرحلة التحليل، لا يعرف المصرِّف ما الذي يشير إليه المعرّف int. أنوع أم ثابت صحيح؟ إذا كان يشير إلى ثابت صحيح، وكان S [4] قد يُعالَج كقيمة byte ثابتة، فإن السطر أعلاه تعريف صالح لنوع مصفوفة عادي.

لحسن الحظ، يعرف مصرِّف Go الآن أن S [4] لن يكون ثابتاً أبداً، لذا يحاول دائماً اعتبار السطر أعلاه ليس تعريف نوع مصفوفة عادي. فلا تحدث أي التباسات هنا. ولو كانت عناصر النصوص الثابتة ثوابت، لما كانت صيغة تعريف وسيط النوع المبسطة ممكنة.

هناك مقترح للسماح لـ Go بدعم المصفوفات الثابتة (وقيم مركّبة ثابتة أخرى). لكن تصميم الأجناس العامة المخصصة في Go كاد يحكم على ذلك المقترح بالإعدام. لأنه، وبالمثل، ستؤدي الشيفرة التالية إلى التباسات في التحليل إذا كان يمكن تعريف قيم المصفوفات كثوابت.


const A = [2]int{1, 2}
type BoolArray [A [1] * int]bool

بالمناسبة، النصوص الفرعية أيضاً ليست ثوابت أبداً

لا يُصرَّف سطر الشيفرة التالي في Go:


const _ = "Google"[:2]

في المقابل، يُصرَّف السطر التالي:


var _ = "Google"[:2]

بحكم هذه الحقيقة، سيطبع البرنامج التالي 128 0 (السبب مشروح هنا).


package main

const S = "Go"

var a byte = 64 << len(S) / 2
var b byte = 64 << len(S[:]) / 2

func main() {
	println(a, b) // 128 0
}

لذا فإن جعل النصوص الفرعية للنصوص الثابتة ثوابت سيكسر أيضاً التوافق العكسي.