range هي صيغة for التي تمشي على مجموعة. تعطيك قيمة أو قيمتين في كل تكرار، وما هاتان القيمتان يعتمد على النوع الذي تمرّ عليه:
| المعامل | القيمة الأولى | القيمة الثانية | ملاحظات |
|---|---|---|---|
شريحة []T، مصفوفة [n]T | الفهرس int | العنصر T، نسخة | |
مؤشر إلى مصفوفة *[n]T | الفهرس | العنصر | |
| نص | فهرس البايت int | rune | يفكّ ترميز UTF-8 |
خريطة map[K]V | المفتاح K | القيمة V | ترتيب عشوائي |
قناة chan T | العنصر T | لا شيء | حتى تُغلق القناة |
عدد صحيح n | من 0 إلى n ناقص 1 | لا شيء | Go 1.22 |
func(yield func() bool) | لا شيء | لا شيء | Go 1.23 |
func(yield func(V) bool) | V | لا شيء | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
اختيار المتغيّرات التي تحصل عليها
for i, v := range s {} // both
for i := range s {} // index (or key) only
for _, v := range s {} // value only
for range s {} // neither: just repeat len(s) times
تعريف متغيّر لا تستخدمه خطأ ترجمة، فاحذف ما لا تحتاجه أو اجعله فارغًا. وfor range 3 {} أقصر طريقة لتكرار شيء ثلاث مرات.
القيمة نسخة
في كل تكرار يُنسخ العنصر إلى متغيّر القيمة. تغيير المتغيّر لا يغيّر المجموعة:
النسخ يكلّف وقتًا أيضًا عندما تكون العناصر بنى كبيرة. لشريحة من البنى الكبيرة، تتجنّب for i := range s مع s[i] النسخ.
النصوص تعطي runes
المرور على نص يفكّ ترميز UTF-8. القيمة الأولى هي إزاحة البايت التي يبدأ عندها كل حرف، فقد تقفز فوق أرقام:
للمشي على البايتات بدل الأحرف استخدم حلقة بفهرس، for i := 0; i < len(s); i++، أو مرّ على []byte(s). راجع صفحة الـ runes والبايتات لتفاصيل UTF-8.
الخرائط: ترتيب عشوائي
ترتيب المرور على الخريطة غير محدّد، ووقت التشغيل يجعله عشوائيًا كي لا تعتمد الشيفرة على ترتيب واحد. للمخرجات الحتمية رتّب المفاتيح:
حذف المدخلات أثناء المرور آمن، والمدخل المحذوف الذي لم تصل إليه الحلقة لا يظهر. إضافة المدخلات مسموحة لكنها غير متوقّعة: قد يظهر مفتاح جديد لاحقًا في الحلقة نفسها وقد لا يظهر. تطبع fmt.Println الخريطة بمفاتيح مرتّبة، ولهذا يكون السطر الأخير ثابتًا.
القنوات: حتى الإغلاق
المرور على قناة يستقبل القيم حتى تُغلق القناة وتفرغ. إذا لم يغلقها أحد تتوقّف الحلقة إلى الأبد (وإذا توقّفت كل goroutine يوقف وقت التشغيل البرنامج برسالة fatal error: all goroutines are asleep - deadlock!):
المرسل هو من يغلق القناة، لا المستقبل أبدًا. راجع صفحة القنوات للقنوات المخزّنة وselect.
الأعداد الصحيحة (Go 1.22)
تعدّ range n من 0 حتى n دون أن تشمله. نوع متغيّر الحلقة هو نوع n:
for i := range 3 {} // i is int: 0, 1, 2
for i := range uint8(3) {} // i is uint8
for range 0 {} // runs zero times
قيمة n السالبة تعمل صفر مرات أيضًا. وتُقيَّم n مرة واحدة، قبل أن تبدأ الحلقة.
ماذا تقيّم range ومتى
يُقيَّم التعبير الذي بعد range مرة واحدة، قبل التكرار الأول. في الشريحة تستخدم range بعدها ترويسة الشريحة تلك، فيكون طولها ثابتًا:
لا تُزار 99 الملحقة أبدًا لأن طول الحلقة ثُبّت عند 3. لكن nums[2] = 30 تُرى، لأن nums في تلك اللحظة ما زالت تتشارك مصفوفتها الخلفية مع الشريحة التي يُمرّ عليها. بدّل السطرين فتختفي 30 أيضًا: تتجاوز append السعة 3، فتحجز مصفوفة جديدة، وتذهب الكتابة إليها بدلًا من ذلك. المرور على قيمة مصفوفة ينسخ المصفوفة كلها أولًا، فلا يُرى التغيير على arr[2]؛ مرّ على &arr أو arr[:] لتتجنّب النسخ.
range على الدوال (Go 1.23)
منذ Go 1.23 تقبل range أيضًا دوال المكرِّر. يأخذ المكرِّر دالة رد النداء yield، ويستدعيها مرة لكل قيمة، ويتوقّف عندما تعيد yield القيمة false (وهذا يحدث عندما ينفّذ جسم الحلقة break أو return):
iter.Seq[V] وiter.Seq2[K, V] هما الاسمان القياسيان لشكلي المكرِّر. تعيدهما المكتبة القياسية من slices.All وslices.Values وslices.Backward وmaps.Keys وmaps.Values وmaps.All، وفي Go 1.24 من strings.SplitSeq وstrings.Lines وbytes.SplitSeq. يجب أن يحترم المكرِّر القيمة التي تعيدها yield: استدعاء yield مرة أخرى بعد أن أعادت false يسبّب panic.
متغيّرات الحلقة لكل تكرار (Go 1.22)
منذ Go 1.22 تكون المتغيّرات التي تعرّفها for ... range جديدة في كل تكرار. التقاط v في إغلاق أو goroutine يلتقط قيمة ذلك التكرار، لا متغيّرًا مشتركًا ينتهي بحمل العنصر الأخير. قبل Go 1.22 كنت تحتاج v := v داخل الحلقة؛ وصار هذا السطر زائدًا الآن. يحدّد القاعدةَ إصدارُ go في go.mod، فتحتفظ الوحدة القديمة بالسلوك القديم.
أخطاء شائعة
- أخذ عنوان متغيّر القيمة. قبل Go 1.22 كانت
ptrs = append(ptrs, &v)تخزّن العنوان نفسه كل مرة. منذ 1.22 تخزّن عناوين مختلفة، لكن كلًا منها يشير إلى نسخة، لا إلى عنصر الشريحة. استخدم&items[i]للإشارة داخل الشريحة. - توقّع ترتيب الخريطة. المخرجات التي تبدو مرتّبة في اختبار صغير لن تبقى مرتّبة.
- المرور على قناة لا يغلقها أحد. تنتظر الحلقة، والـ goroutine التي تشغّلها، إلى الأبد.
- المرور على مصفوفة كبيرة بالقيمة. تُنسخ المصفوفة كلها أولًا. مرّ على شريحة منها بدلًا من ذلك.
الأسئلة الشائعة
ماذا تعيد range في Go؟
يعتمد ذلك على المعامل. للشريحة أو المصفوفة: الفهرس والعنصر. للنص: فهرس البايت والـ rune. للخريطة: المفتاح والقيمة. للقناة: كل قيمة مستقبَلة. لعدد صحيح n (Go 1.22): من 0 إلى n ناقص 1. لدالة مكرِّر (Go 1.23): ما تعطيه الدالة. يمكنك حذف المتغيّر الثاني، أو تجاهل الأول بـ _.
لماذا لا يغيّر تعديل قيمة range شريحتي؟
متغيّر القيمة نسخة من العنصر. for _, v := range items { v.Price = 0 } تغيّر النسخة فقط. استخدم الفهرس بدلًا من ذلك: for i := range items { items[i].Price = 0 }، أو مرّ على شريحة من المؤشرات.
هل يمكنني المرور بـ range على عدد صحيح في Go؟
نعم، منذ Go 1.22: for i := range 5 { ... } تعمل وi من 0 إلى 4. وfor range 5 { ... } تكرّر خمس مرات دون متغيّر. يجب أن يذكر go.mod للوحدة go 1.22 أو أحدث.
هل الحذف من خريطة أثناء المرور عليها آمن؟
نعم. حذف مدخل لم تصل إليه الحلقة بعد يعني أنه لن يظهر، وحذف المدخل الحالي لا بأس به. إضافة مدخلات أثناء الحلقة مسموحة أيضًا، لكن المدخل الجديد قد يُزار وقد لا يُزار، فلا تعتمد على ذلك.