متى يتم تشغيل `init()` في Go؟ تحليل مع أمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

متى يتم تشغيل init() في Go؟ تحليل بمثال عملي



تخيل: أنت تكتب خدمة مصغرة بلغة Go. في إحدى الحزم، قمت بتعريف init() لتحميل ملف الإعدادات. تقوم بتشغيل التطبيق - وفجأة يتعطل مع خطأ nil pointer dereference. أنت في ذهول: "لماذا؟ كان يجب تحميل الإعدادات أولاً!"



هل هذا مألوف؟ إذا كان الأمر كذلك، فأنت لست وحدك. سؤال "متى يتم تشغيل دالة init()؟" هو أحد أكثر الأسئلة شيوعاً على Stack Overflow (آلاف المشاهدات). دعنا نفهم مرة واحدة وإلى الأبد متى وبأي ترتيب يتم تشغيل init() في Go، حتى لا تقع في هذا الفخ مرة أخرى.



ما هي init() ولماذا نحتاجها؟



في Go، init() هي دالة خاصة بدون معاملات وقيمة إرجاع. لا يتم استدعاؤها بشكل صريح. يقوم نظام Go بتشغيلها تلقائياً قبل تنفيذ دالة main().



لماذا نحتاج هذا؟



  • تهيئة المتغيرات العامة التي تتطلب منطقاً معقداً (مثل القراءة من ملف).
  • تسجيل برامج تشغيل قواعد البيانات أو معالجات HTTP.
  • التحقق من البيئة (متغيرات البيئة، توفر الموارد).


لكن القاعدة الأساسية: يجب ألا تعتمد أبداً على ترتيب تنفيذ init() بين الملفات المختلفة لنفس الحزمة. وإليك السبب.



متى يتم تشغيل init() بالضبط؟ الترتيب الدقيق



يقوم Go بتنفيذ init() وفق قواعد صارمة. دعنا نوضحها بالتفصيل.



القاعدة رقم 1: حزمة واحدة - ترتيب واحد

داخل الحزمة الواحدة، يتم تنفيذ init() بترتيب إعلان الملفات (حسب الترتيب الأبجدي لأسماء الملفات). لكن هذه تفاصيل تنفيذية - من الأفضل عدم الاعتماد عليها.



القاعدة رقم 2: تهيئة المتغيرات أولاً، ثم init()

قبل استدعاء init()، يقوم Go بتهيئة جميع المتغيرات العامة في الحزمة. إذا كان متغير يعتمد على متغير آخر من نفس الحزمة - فإن ترتيب إعلانها مهم.



package config

var AppName string = "MyApp"var Version string = getVersion() // سيتم استدعاؤها قبل init()

func init() { // هنا AppName و Version متاحان بالفعل println("Init started for", AppName)}

func getVersion() string { return "1.0.0"}


القاعدة رقم 3: الحزم المستوردة تتم تهيئتها أولاً

هذه هي القاعدة الأهم. إذا كانت الحزمة A تستورد الحزمة B، فسيتم أولاً تنفيذ جميع دوال init() في الحزمة B، ثم في A. يقوم Go ببناء رسم بياني للتبعيات وينفذ التهيئة "من الأسفل إلى الأعلى".



// حزمة databasepackage database

func init() { println("1. database init")}

// حزمة main (تستورد database)package main

import "myapp/database"

func init() { println("2. main init")}

func main() { println("3. main function")}

// الإخراج:// 1. database init// 2. main init// 3. main function


القاعدة رقم 4: عدة دوال init() في ملف واحد - ممكن ولكن غير مستحسن

يمكن أن يكون هناك عدة دوال init() في ملف واحد. سيتم تنفيذها بترتيب الإعلان. لكن هذا يعقد قراءة الكود. من الأفضل دمج المنطق في دالة واحدة.



مثال حي: ماذا يحدث إذا أخطأت في الترتيب؟



دعنا نحاكي خطأً نموذجياً للمبتدئين. لنفترض أن لدينا حزمة config تقرأ الإعدادات من ملف، وحزمة app تستخدم هذه الإعدادات.



// config.gopackage config

var Settings map[string]string

func init() { // محاكاة قراءة ملف Settings = map[string]string{ "port": "8080", "host": "localhost", } println("Config loaded")}

// app.gopackage app

import "myapp/config"

var ServerPort string

func init() { // خطأ: إذا لم يتم تهيئة config.Settings بعد

المدونات

توصيات الكتب