Menu
flag Ar iconالعربيةdown icon

الدوال في Golang: التعريف والاستدعاء وتمرير الدوال

كيف تعرّف الدوال وتستدعيها في Go: المعاملات، والمعاملات التي تتشارك نوعًا، والقيم المعادة، والتمرير بالقيمة، والدوال كقيم يمكنك تخزينها وتمريرها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تعريف دالة

تعريف الدالة هو func، ثم اسم، ثم المعاملات، ثم نوع النتيجة، ثم جسم. يأتي النوع بعد اسم المعامل لا قبله.

تأخذ add قيمتين من النوع int وتعيد int. أما greet فلا تعيد شيئًا، فلا نوع نتيجة في توقيعها ولا يحتاج جسمها return.

الدالة التي تعرّف نتيجة يجب أن تنتهي بعبارة منهية. إذا أمكن لمسار ما الوصول إلى القوس الختامي دون عودة، يتوقّف المترجم برسالة missing return.

معاملات تتشارك نوعًا

المعاملات المتتالية من النوع نفسه يمكن أن تتشارك اسم نوع واحدًا. func add(a, b int) int تعني تمامًا ما تعنيه func add(a int, b int) int. والخلط مسموح أيضًا:

func scale(x, y float64, factor int) (float64, float64)

هنا x وy من النوع float64 وfactor من النوع int.

القيم المعادة

يمكن أن تعيد الدالة صفرًا أو قيمة واحدة أو عدة قيم. النتائج المتعدّدة توضع بين قوسين، ويستقبلها المستدعي بإسناد متعدّد:

يطبع هذا 3 2. الزوج (value, error) هو أشيع استخدام لهذه الميزة في Go، وله صفحته الخاصة عن القيم المعادة المتعدّدة.

الوسائط تُنسخ

تمرّر Go كل وسيط بالقيمة. تعمل الدالة على نسخة، فالإسناد إلى معامل لا يغيّر متغيّر المستدعي أبدًا.

المخرجات:

{1 2}
{100 2}
[99 2 3]

السطر الثالث يفاجئ الناس. قيمة الشريحة ترويسة صغيرة (مؤشر وطول وسعة). النسخة تشير إلى المصفوفة الخلفية نفسها، فالكتابة في s[0] تكتب في عنصر المستدعي. لكن append داخل الدالة قد تحجز مصفوفة جديدة، ولا يرى المستدعي تلك الترويسة الجديدة أبدًا. إذا كانت الدالة تكبّر شريحة، فأعد الشريحة الجديدة. والمنطق نفسه ينطبق على الخرائط: تستطيع الدالة إضافة مفاتيح يراها المستدعي، لأن قيمة الخريطة تشير إلى بيانات مشتركة.

استخدم معامل مؤشر عندما يجب أن تعدّل الدالة متغيّر المستدعي، أو عندما تكون القيمة بنية كبيرة لا تريد نسخها في كل استدعاء.

الدوال قيم

للدالة نوع يُكتب دون الاسم: func(int, int) int. يمكنك تخزين دالة في متغيّر، ووضعها في خريطة أو شريحة، وتمريرها إلى دالة أخرى.

square دالة مجهولة مُسندة إلى متغيّر. ويمكن للدوال المجهولة أيضًا قراءة متغيّرات النطاق المحيط وتغييرها، وهذا يجعلها إغلاقات.

نوع الدالة المسمّى يجعل التوقيعات أسهل قراءة عندما يتكرّر الشكل نفسه في أماكن كثيرة:

type Transform func(int) int

func apply(xs []int, f Transform) []int

القيمة الصفرية لنوع الدالة هي nil. استدعاء دالة nil يسبّب panic برسالة invalid memory address or nil pointer dereference، لذا افحص دوال رد النداء الاختيارية قبل استدعائها. ولا تُقارن قيم الدوال إلا بـ nil، ولا تُقارن ببعضها أبدًا.

العودية

يمكن للدالة أن تستدعي نفسها. لا تملك Go تحسين الاستدعاء الذيلي، لكن مكدّسات الـ goroutines تنمو عند الحاجة، فأعماق العودية العادية لا مشكلة فيها.

for i := range 6 تعدّ من 0 إلى 5 وتحتاج Go 1.22 أو أحدث.

ما لا تملكه الدوال في Go

  • لا تحميل زائد. لا يمكن لدالتين في حزمة واحدة أن تتشاركا اسمًا، حتى مع معاملات مختلفة.
  • لا وسائط افتراضية أو مسمّاة. يُمرَّر كل معامل في كل استدعاء، بالترتيب. بنية الإعدادات تعطيك حقولًا مسمّاة واختيارية: NewServer(Config{Port: 8080}).
  • لا دوال مسمّاة متداخلة. داخل جسم دالة لا يمكنك إلا تعريف دوال مجهولة، مسندة عادة إلى متغيّر.

قوائم الوسائط متغيّرة الطول مدعومة عبر الدوال المتغيّرة العدد.

الدوال المُصدَّرة وغير المُصدَّرة

الدالة التي يبدأ اسمها بحرف كبير (Parse) مُصدَّرة ويمكن استدعاؤها من حزم أخرى. أما الاسم بحرف صغير (parse) فمرئي داخل حزمته فقط. لا توجد كلمات public أو private؛ حالة الحرف الأول هي القاعدة كلها.

أخطاء شائعة

  • توقّع أن يصل تغيير المعامل إلى المستدعي. الإسناد إلى معامل يغيّر النسخة فقط. أعد القيمة الجديدة أو خذ مؤشرًا.
  • الإلحاق داخل دالة وتجاهل النتيجة. func add(s []int) { s = append(s, 1) } لا أثر لها على طول شريحة المستدعي. اكتب func add(s []int) []int واستخدم s = add(s).
  • النتائج غير المستخدمة. تسمح Go بتجاهل قيمة معادة. لا يبلغ go vet عن خطأ مُهمل، لكن أدوات الفحص مثل errcheck (جزء من golangci-lint) تبلغ عنه. استخدم _ = فقط عندما يكون التجاهل قرارًا.

الأسئلة الشائعة

كيف تعرّف دالة في Go؟

استخدم الكلمة func، ثم اسمًا، ثم قائمة معاملات تأتي أنواعها بعد أسمائها، ثم نوع النتيجة: func add(a, b int) int { return a + b }. الدالة التي لا نتيجة لها تحذف النوع، والدالة ذات النتائج المتعدّدة تسردها بين قوسين: func divmod(a, b int) (int, int).

هل تمرّر Go الوسائط بالقيمة أم بالمرجع؟

بالقيمة دائمًا. تحصل الدالة على نسخة من كل وسيط. في int أو بنية تكون هذه النسخة مستقلّة عن متغيّر المستدعي. أما الشرائح والخرائط والقنوات والمؤشرات فهي قيم صغيرة تشير إلى بيانات مشتركة، فالتغييرات على العناصر التي تشير إليها يراها المستدعي، بينما إعادة إسناد المعامل نفسه لا يراها. مرّر مؤشرًا (*T) عندما يجب أن تغيّر الدالة متغيّر المستدعي.

هل يمكن تمرير دالة كمعامل في Go؟

نعم. الدوال قيم لها نوع مثل func(int) int. عرّف معاملًا من هذا النوع ومرّر أي دالة بتوقيع مطابق، مسمّاة أو مجهولة: func apply(xs []int, f func(int) int).

هل تدعم Go تحميل الدوال الزائد أو المعاملات الافتراضية؟

لا. يجب أن يكون اسم كل دالة في الحزمة فريدًا، ويجب تمرير كل معامل في كل استدعاء. البدائل المعتادة أسماء مختلفة (NewServer وNewServerWithTLS)، أو بنية إعدادات، أو المعاملات المتغيّرة العدد، أو الخيارات الوظيفية.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن