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

الحزم والاستيراد في Golang: الأسماء المُصدَّرة وتعدّد الملفات

كيف تعمل الحزم في Go: عبارة package، استيراد الحزم القياسية وحزم الوحدات، الأسماء المُصدَّرة وغير المُصدَّرة، تقسيم حزمة على عدة ملفات، الحزم الداخلية، الأسماء البديلة للاستيراد، الاستيراد الفارغ، وخطأ "undefined" من go run main.go.

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

كل ملف Go ينتمي إلى حزمة (package)، يُصرَّح بها في سطره الأول. تستخدم الشيفرة في حزمة حزمةً أخرى باستيرادها ووضع اسم الحزمة قبل الأسماء. ولا يمكن استخدام إلا الأسماء التي تبدأ بحرف كبير من خارج الحزمة.

fmt وmath وstrings حزم من المكتبة القياسية. تأتي مع Go، فـ"إضافة" واحدة منها تعني كتابة اسمها في كتلة import. لا يُنزَّل شيء. وfmt.Println وmath.Sqrt وstrings.Repeat تبدأ بحرف كبير لأنها مُصدَّرة.

عبارة package

أول سطر ليس تعليقًا في كل ملف .go هو package name. يجب أن تستخدم كل الملفات في مجلد واحد الاسم نفسه، وتشكّل معًا حزمة واحدة. المجلد الذي فيه اسما حزمتين مختلفان لا يُترجم:

found packages greeting (b.go) and greet (greet.go) in /home/ana/shop/greet

(الاستثناء الوحيد ملفات الاختبار، التي يمكن أن تستخدم package name_test.)

الحزمة package main خاصة: تنتج ملفًا تنفيذيًا، ويبدأ البرنامج من func main() فيها. وكل حزمة أخرى مكتبة معدّة للاستيراد.

اصطلاحًا يطابق اسم الحزمة آخر عنصر في مسار المجلد: الشيفرة في shop/greet هي package greet. أسماء الحزم قصيرة، بحروف صغيرة، كلمة واحدة، دون شرطات سفلية أو حروف كبيرة في الوسط: strconv وhttptest وgreet، لا string_utils ولا greetHelpers.

استيراد الحزم

مسار الاستيراد نص. للمكتبة القياسية هو مسار الحزمة في شجرة مصادر Go؛ ولكل ما عدا ذلك يبدأ بمسار وحدة:

import (
	"fmt"           // standard library
	"net/http"      // standard library, nested path
	"math/rand/v2"  // standard library, version 2 of math/rand

	"github.com/google/uuid"   // another module (needs go get)
	"example.com/shop/greet"   // a package in your own module
)

المسار هو مكان الحزمة؛ والاسم هو ما تكتبه في الشيفرة. لـ net/http الاسم http، ولـ math/rand/v2 الاسم rand. تكتب http.Get، ولا تكتب أبدًا net/http.Get.

يرتّب gofmt وgoimports الكتلة، وتُفصل اصطلاحًا استيرادات المكتبة القياسية عن البقية بسطر فارغ.

الاستيرادات غير المستخدمة خطأ ترجمة:

./main.go:5:2: "os" imported and not used

المحرّرات التي تدعم Go تشغّل goimports عند الحفظ، فيضيف الاستيرادات القياسية الناقصة ويزيل غير المستخدمة نيابة عنك.

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

تحدّد قاعدة واحدة الرؤية في Go: إذا بدأ الاسم بحرف كبير فهو مُصدَّر. ينطبق ذلك على الدوال والأنواع والمتغيّرات والثوابت وحقول البنى والتوابع. لا توجد كلمات public أو private أو protected.

الاسممرئي خارج الحزمة؟
func Hello()نعم
func hello()لا
type User structنعم
الحقل User.Nameنعم
الحقل User.emailلا
const MaxSizeنعم
var defaultTimeoutلا

محاولة استخدام اسم غير مُصدَّر من حزمة أخرى تعطي خطأ undefined:

./main.go:11:20: undefined: greet.prefix

القاعدة مهمّة حتى داخل ملف واحد، لأن حزمًا أخرى تفحص أنواعك. لا ترى encoding/json إلا الحقول المُصدَّرة:

يُستبعد token من JSON بصمت. هذه ميزة عندما يكون الحقل بيانات خاصة، ومفاجأة عندما تنسى جعل الحرف الأول كبيرًا في حقل أردته.

تقسيم حزمة على عدة ملفات

يمكن أن تمتد الحزمة على عدد ما تشاء من الملفات. كل ملف في المجلد له سطر package نفسه يرى كل اسم معرّف في الملفات الأخرى، مُصدَّرًا كان أم لا، دون استيراد أي شيء:

shop/
├── go.mod        (module example.com/shop)
├── main.go
└── math.go
// main.go
package main

import "fmt"

func main() {
	fmt.Println(total(2, 3))
}
// math.go
package main

func total(a, b int) int { return a + b }

تستدعي main.go الدالة total مباشرة. لا يوجد #include ولا استيراد بين ملفات الحزمة نفسها.

خطأ "undefined" من go run main.go

هذه أكثر مشكلة حزم يُبحث عنها في Go، ولها سبب واحد. تشغيل الملف الذي يحتوي main وحده يترجم ذلك الملف وحده:

go run main.go
# command-line-arguments
./main.go:6:14: undefined: total

command-line-arguments هو الاسم الذي تعطيه Go لحزمة مبنية من قائمة ملفات، وmath.go ليس في تلك القائمة. شغّل الحزمة كلها بدلًا من ذلك:

go run .
5

والأمر نفسه ينطبق على go build main.go. استخدم go run . وgo build (أو go build ./cmd/app) ولن تظهر المشكلة أبدًا. إذا استمرت مع go run .، فتحقّق من أن الملفين يقولان package main وأن أيًا منهما لا يستبعده قيد بناء (//go:build) أو لاحقة _test.go.

حزمك الخاصة

كل مجلد فرعي في وحدتك حزمة منفصلة. استوردها بمسار الوحدة من go.mod مع المجلد:

shop/
├── go.mod          (module example.com/shop)
├── main.go
└── greet/
    └── greet.go
// greet/greet.go
package greet

const prefix = "Hi, "

// Hello returns a greeting for name.
func Hello(name string) string {
	return prefix + name
}
// main.go
package main

import (
	"fmt"

	"example.com/shop/greet"
)

func main() {
	fmt.Println(greet.Hello("Ana"))
}
Hi, Ana

أمران يُربكان الناس هنا. الاستيراد هو مسار الوحدة مع المجلد، لا مسار نسبي: "./greet" لا يعمل في وضع الوحدات. ويبقى prefix مخفيًا عن main لأنه يبدأ بحرف صغير؛ Hello وحدها جزء من الواجهة البرمجية للحزمة.

الحزم الداخلية

المجلد المسمّى internal يقيّد من يستطيع استيراد ما بداخله. يمكن لأي حزمة جذرها shop/ استيراد الشيفرة في shop/internal/store، ولا يمكن لأي شيء خارجه. والوحدة الأخرى التي تحاول تحصل على:

use of internal package example.com/shop/internal/store not allowed

استخدم internal/ للشيفرة التي تريد مشاركتها بين حزمك دون التزام بها أمام بقية العالم. يمكنك تغيير واجهتها بحرية، لأن لا شيفرة خارجية تستطيع الاعتماد عليها.

الأسماء البديلة للاستيراد

أعطِ الاستيراد اسمًا محليًا مختلفًا بكتابة الاسم قبل المسار. السبب المعتاد حزمتان بالاسم نفسه:

الحزمتان كلتاهما اسمها rand، فتحتاج واحدة على الأقل اسمًا بديلًا. استخدم الأسماء البديلة للتعارضات وللأسماء غير الواضحة؛ ولا تعد تسمية الحزم لمجرّد اختصارها، فالقرّاء يعرفون strconv لا sc الخاص بك.

الاستيراد الفارغ واستيراد النقطة

الاستيراد الفارغ (_). يستورد حزمة لآثارها الجانبية فقط: تُنفَّذ متغيّرات حزمتها ودوال init، لكنك لا تستخدم أيًا من أسمائها. تسجّل مشغّلات قواعد البيانات ومفكّكات الصور نفسها بهذه الطريقة:

import (
	"database/sql"

	_ "github.com/lib/pq" // registers the "postgres" driver with database/sql
)

دون _ سيكون الاستيراد خطأ استيراد غير مستخدم.

استيراد النقطة (.). يضع الأسماء المُصدَّرة للحزمة مباشرة في نطاق ملفك، فتكتب Println بدل fmt.Println. يخفي مصدر الأسماء ولا يُنصح به خارج بعض أنماط الدوال المساعدة في الاختبارات.

حلقات الاستيراد

لا تسمح Go لحزمتين باستيراد كل منهما الأخرى، مباشرة أو عبر سلسلة:

package example.com/shop
	imports example.com/shop/greet from main.go
	imports example.com/shop/other from c.go
	imports example.com/shop/greet from o.go: import cycle not allowed

يسرد الخطأ السلسلة. إصلاحان يغطّيان كل الحالات تقريبًا:

  1. انقل الشيفرة التي تحتاجها الحزمتان إلى حزمة ثالثة أدنى مستوى تستوردها الاثنتان.
  2. إذا كانت الحزمة A تحتاج فقط إلى استدعاء شيء في B، فعرّف واجهة صغيرة في A ودع نوع B يحقّقها. لا تعود A تستورد B.

الحلقة تعني عادة أن حدود الحزم رُسمت حول طبقات من الشيفرة تنتمي فعلًا إلى بعضها، فهي أيضًا إشارة لإعادة النظر في التقسيم.

أخطاء شائعة

  • تشغيل ملف واحد. go run main.go في حزمة متعدّدة الملفات: استخدم go run ..
  • الاستيرادات النسبية. يفشل import "./utils" في وضع الوحدات. استخدم "example.com/yourmodule/utils".
  • أسماء بحرف صغير قصدت تصديرها. الدالة أو الحقل أو التابع الذي تحتاجه حزم أخرى يجب أن يبدأ بحرف كبير.
  • اسم حزمة لا يطابق المجلد. مسموح لكنه مربك: يقول مسار الاستيراد utils وتقول الشيفرة helpers.X. أبقهما متطابقين.
  • أسماء حزم جامعة. util وcommon وmisc لا تقول شيئًا عن محتواها وتميل إلى أن تصير مغناطيسًا لحلقات الاستيراد. سمِّ الحزم بما تقدّمه: money وauth وslug.

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

لماذا تقول Go إن دالة في الحزمة نفسها غير معرّفة (undefined)؟

على الأرجح شغّلت go run main.go. تسمية ملف تترجم ذلك الملف وحده، فتنقص الدوال المعرّفة في ملفات أخرى من الحزمة نفسها: undefined: total. شغّل go run . بدلًا من ذلك، الذي يترجم كل ملفات .go في المجلد. وتحقّق أيضًا من أن لكل ملف سطر package نفسه.

كيف أضيف الحزمة fmt في Go؟

fmt جزء من المكتبة القياسية ويأتي مع Go، فلا شيء لتثبيته. أضف import "fmt" بعد سطر package واستدعِ دوالها بالبادئة fmt.، مثلًا fmt.Println("hi"). والأمر نفسه ينطبق على strings وos وtime وnet/http وكل حزمة قياسية أخرى.

كيف أستورد حزمتي الخاصة في Go؟

استوردها بمسار الوحدة مع المجلد. في وحدة اسمها example.com/shop تُستورد الحزمة الموجودة في المجلد greet بـ import "example.com/shop/greet" وتُستخدم بـ greet.Hello(). لا يرى المستورد إلا الأسماء التي تبدأ بحرف كبير.

ما الفرق بين المُصدَّر وغير المُصدَّر في Go؟

الاسم الذي يبدأ بحرف كبير (Hello وTotal وUser.Name) مُصدَّر ويمكن استخدامه من حزم أخرى. والاسم بحرف صغير (hello وtotal وuser.name) غير مُصدَّر ومرئي داخل حزمته فقط. لا توجد كلمات public أو private.

كيف أصلح خطأ "import cycle not allowed" في Go؟

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

Coddy programming languages illustration

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

ابدأ الآن