العودة إلى المدونة

الكلمة المفتاحية type في Go: الأنواع المُعرَّفة والأسماء البديلة والدوال التابعة

كيف تعمل الكلمة المفتاحية type في Go: الأنواع المُعرَّفة مقابل الأسماء البديلة، والأنواع الأساسية والتحويلات، ولماذا لا تنتقل الدوال التابعة، وأنواع الدوال مثل http.HandlerFunc، والأنواع العامة، و type switch.

الكلمة المفتاحية type في Go: الأنواع المُعرَّفة والأسماء البديلة والدوال التابعة

تمنح الكلمة المفتاحية type نوعًا ما اسمًا. ولها صيغتان. الصيغة type UserID int64 تعريف نوع (type definition)، وهي تنشئ نوعًا جديدًا تمامًا له هويته الخاصة ومجموعة دواله التابعة (methods) الخاصة. أما الصيغة type Handler = http.Handler فتصريح عن اسم بديل (alias declaration)، وهي تضيف اسمًا ثانيًا لنوع موجود أصلًا. والصيغة التي تختارها تحدد هل يسمح لك المترجم بالخلط بين الاسمين، وهل يمكنك إضافة دوال تابعة (مواصفات Go).

الخلاصة

  • الصيغة type Name Underlying تعرّف نوعًا جديدًا. والصيغة type Name = Other تصرّح عن اسم بديل. والكتلة type ( ... ) تجمع عدة تصريحات.
  • النوع المُعرَّف مختلف عن كل نوع آخر، حتى عن نوع له النوع الأساسي نفسه. فلا يمكن تمرير UserID حيث يُتوقَّع OrderID أو int64 دون تحويل صريح.
  • الثوابت غير المحددة النوع والقيم غير المُسمّاة، مثل قيم map الحرفية، ما زال يمكن إسنادها إلى نوع مُعرَّف دون تحويل، لذا يُترجم cancelOrder(42).
  • يبدأ النوع المُعرَّف دون أي دوال تابعة. فالنوع type Timestamp time.Time يفقد Format. وإذا أردت الاحتفاظ بها، فضمّن time.Time داخل struct.
  • لا يمكنك التصريح عن دوال تابعة إلا على الأنواع التي تنتمي إلى حزمتك. فالسطر func (d time.Duration) ... يفشل بالخطأ cannot define new methods on non-local type.
  • الاسم البديل هو النوع نفسه تحت اسم آخر. و byte و rune و any أسماء بديلة. ويمكن للأسماء البديلة أن تأخذ معاملات أنواع (type parameters) منذ Go 1.24.
  • يمكن أن تكون لأنواع الدوال، مثل http.HandlerFunc، دوال تابعة، وبهذه الطريقة تحوّل Go دالة عادية إلى قيمة interface.
  • تأخذ الأنواع العامة معاملات أنواع، كما في Page[T any]. ومنذ Go 1.27 يمكن للدوال التابعة أيضًا أن تصرّح عن معاملات أنواع خاصة بها.

كيف تصرّح عن نوع في Go؟

اكتب type ثم اسمًا ثم النوع الذي يُبنى عليه. ويمكن لعدة تصريحات أن تشترك في كتلة type ( ... ) واحدة، بالطريقة نفسها التي تعمل بها كتل const و var:

example.gogo
package main

import (
	"fmt"
	"net/http"
	"time"
)

type (
	UserID  int64
	OrderID int64

	Order struct {
		ID        OrderID
		UserID    UserID
		CreatedAt time.Time
	}

	OrderStore interface {
		Get(id OrderID) (Order, error)
	}

	Handler = http.Handler
)

func main() {
	o := Order{ID: 1001, UserID: 42}
	fmt.Println(o.ID)
}

يطبع هذا البرنامج 1001. تحتوي الكتلة على أربعة تعريفات أنواع واسم بديل واحد. و struct و interface هما أكثر ما يظهر في الجانب الأيمن من التصريح، ولكل منهما كلمة مفتاحية خاصة به، يشرحها مقال الكلمات المفتاحية في Go: شرح جميع الكلمات الـ 25. وللـ structs مثل Order دليل خاص بها في مقال الكلمة المفتاحية struct في Go. وللـ interfaces مثل OrderStore دليل خاص بها في مقال الكلمة المفتاحية interface في Go.

تتبع أسماء الأنواع قواعد الظهور المعتادة. فالنوع Order مُصدَّر لأنه يبدأ بحرف كبير، أما orderRow فلن يكون مرئيًا إلا داخل حزمته. ويشرح مقال الكلمة المفتاحية package في Go الأسماء المُصدَّرة بتفصيل أكبر.

ويمكنك أيضًا التصريح عن نوع داخل دالة. عندها لا يكون مرئيًا إلا في تلك الدالة، وهذا يناسب الأشكال التي تُستخدم مرة واحدة، مثل جسم طلب:

example.gogo
func main() {
	type loginRequest struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req loginRequest
	err := json.Unmarshal([]byte(`{"email":"[email protected]","password":"hunter2"}`), &req)
	fmt.Println(req.Email, err)
}

يطبع هذا البرنامج [email protected] <nil>. ولا يمكن أن تكون للنوع المحلي دوال تابعة، لأن تصريحات الدوال التابعة لا توجد إلا على مستوى الحزمة.

ما النوع المُعرَّف في Go؟

هو نوع مُصرَّح عنه بالصيغة type Name Underlying، دون =. وتسمّي المواصفات هذا تعريف نوع. لكل نوع مُعرَّف نوع أساسي (underlying type)، وهو الذي يحدد كيف تُخزَّن قيمه وأي العوامل تعمل عليها. فالنوعان UserID و OrderID أعلاه نوعهما الأساسي int64، لذا يمكنك جمعهما ومقارنتهما وطباعتهما كأي int64.

أما ما لا يشتركان فيه فهو الهوية. فالنوع المُعرَّف مختلف دائمًا عن كل نوع آخر، لذا يرفض المترجم الخلط بينهما:

example.gogo
package main

import "fmt"

type UserID int64
type OrderID int64

func cancelOrder(id OrderID) {
	fmt.Println("cancelling order", id)
}

func main() {
	var user UserID = 42
	cancelOrder(user)
}
example.texttext
./main.go:14:14: cannot use user (variable of int64 type UserID) as OrderID value in argument to cancelOrder

هذا الخطأ هو سبب جدوى تعريف أنواع للمعرّفات (IDs). فالدالة ذات التوقيع refund(userID, orderID int64) تقبل وسيطيها بأي ترتيب. أما مع UserID و OrderID فيصبح تبديلهما خطأ ترجمة، بدلًا من استرداد مالي يذهب إلى العميل الخطأ.

والقاعدة نفسها تفصل بين النوع المُعرَّف ونوعه الأساسي. فتمرير UserID إلى دالة تأخذ int64 يفشل أيضًا:

example.texttext
./main.go:13:11: cannot use id (variable of int64 type UserID) as int64 value in argument to loadUser

كيف تحوّل بين الأنواع المُعرَّفة؟

اكتب النوع الهدف كأنه استدعاء دالة. يُترجم كل من OrderID(user) و int64(user)، لأن التحويل مسموح متى كان للنوعين النوع الأساسي نفسه. وليس للتحويل أي كلفة وقت التشغيل. فالقيمة تبقى كما هي، ولا يتغير إلا النوع الذي يراه المترجم.

والتحويلات صريحة عن قصد. فحين ترى OrderID(user) في مراجعة كود، تعرف أن أحدهم قرر معاملة معرّف مستخدم على أنه معرّف طلب، ويمكنك أن تسأل عن السبب.

لماذا يمكنك تمرير 42 أو قيمة map حرفية دون تحويل؟

لأن أيًّا منهما ليس له نوع مُعرَّف بعد. لقابلية الإسناد (assignability) قواعد أخرى غير «الأنواع متطابقة»، واثنتان منها تتكرران باستمرار (مواصفات Go).

الأولى تخص الثوابت غير المحددة النوع. فالقيمة 42 ثابت غير محدد النوع، ويمكن أن تصبح أي نوع يستطيع نوعه الأساسي تمثيلها، لذا يُترجم cancelOrder(42) ويطبع cancelling order 42. وهي القاعدة نفسها التي تتيح لك كتابة 5 * time.Second، لأن time.Duration نفسه نوع مُعرَّف نوعه الأساسي int64. ويشرح مقال الكلمة المفتاحية const في Go الثوابت غير المحددة النوع بالتفصيل.

والثانية تخص الأنواع غير المُسمّاة. يمكن إسناد قيمة إلى نوع مُعرَّف حين يكون لهما نوعان أساسيان متطابقان، ويكون أحدهما على الأقل غير مُسمّى. والنوع map[string]string صيغة نوع حرفية (type literal) بلا اسم، لذا تقبله مباشرةً الدالة التي تأخذ Headers:

example.gogo
type Headers map[string]string

func send(h Headers) {
	fmt.Println(len(h), h["Content-Type"])
}

func main() {
	send(map[string]string{"Content-Type": "application/json"})

	raw := map[string]string{"Accept": "text/plain"}
	send(raw)
}

يُترجم الاستدعاءان كلاهما. وتعمل القاعدة بالطريقة نفسها مع []byte و func(...) وقيم struct الحرفية. لكنها لا تسمح أبدًا بالانتقال مباشرةً من نوع مُسمّى إلى آخر، مثل UserID إلى OrderID أو UserID إلى int64.

لماذا لا يرث النوع المُعرَّف الدوال التابعة؟

يحصل النوع المُعرَّف على بنية النوع الأساسي، لكنه لا يحصل على أي من دواله التابعة. وهذا يفاجئ كثيرين في أول مرة يغلّفون فيها time.Time:

example.gogo
package main

import (
	"fmt"
	"time"
)

type Timestamp time.Time

func main() {
	created := Timestamp(time.Now())
	fmt.Println(created.Format(time.RFC3339))
}
example.texttext
./main.go:12:22: created.Format undefined (type Timestamp has no field or method Format)

تبدأ مجموعة الدوال التابعة (method set) فارغة كي يستطيع النوع الجديد أن يتصرف بشكل مختلف عن القديم. فالنوع http.Header هو map[string][]string مع دوال تابعة مثل Get و Set توحّد صيغة أسماء الترويسات، والنوع sort.StringSlice هو []string مع الدوال التابعة التي تحتاجها sort.Interface. ولو كانت الأنواع المُعرَّفة تنسخ كل دالة تابعة من نوعها الأساسي، لاكتسب Timestamp دوالًا تابعة لم تختر أن تعطيه إياها.

أمامك خياران حين تريد استعادة الدوال التابعة القديمة. الأول أن تحوّل إلى النوع الأصلي حيث تحتاج إليها. ودالة String تابعة مكتوبة بهذه الطريقة تتحكم في طريقة طباعة القيمة:

example.gogo
type Timestamp time.Time

func (t Timestamp) String() string {
	return time.Time(t).Format(time.RFC3339)
}

والثاني التضمين (embedding). فالـ struct التي تضمّن time.Time تُرقّي (promote) كل دوالها التابعة، وتضيف أنت دوالك الخاصة بجانبها:

example.gogo
package main

import (
	"fmt"
	"time"
)

type Timestamp struct {
	time.Time
}

func (t Timestamp) ISO() string {
	return t.UTC().Format(time.RFC3339)
}

func main() {
	created := Timestamp{time.Date(2026, 9, 25, 14, 30, 0, 0, time.UTC)}
	fmt.Println(created.Format("2006-01-02"))
	fmt.Println(created.ISO())
}

يطبع هذا البرنامج 2026-09-25 ثم 2026-09-25T14:30:00Z. يحتفظ التضمين بـ Format و Before و Unix وكل دالة تابعة أخرى لـ time.Time. ويعني أيضًا أن Timestamp يحقق كل interface يحققها time.Time، بما في ذلك json.Marshaler، لذا يُرمَّز إلى JSON على شكل سلسلة نصية بصيغة RFC 3339 بدلًا من كائن.

هل يمكنك إضافة دوال تابعة إلى نوع مدمج أو مستورد؟

لا. يجب التصريح عن الدالة التابعة في الحزمة نفسها التي يوجد فيها نوع مستقبِلها (receiver). فإضافة دالة تابعة إلى time.Duration تفشل:

example.gogo
func (d time.Duration) Minutes10() float64 {
	return d.Minutes() / 10
}
example.texttext
./main.go:5:9: cannot define new methods on non-local type time.Duration

ويحدث الأمر نفسه مع int و string وكل نوع آخر مُصرَّح عنه مسبقًا، ومع الاسم البديل لنوع من حزمة أخرى. تمنع هذه القاعدة حزمتين من إلحاق دوال تابعة متعارضة بالنوع نفسه. وحين تحتاج إلى سلوك إضافي، عرّف نوعك الخاص، كما في type Cents int64، وضع الدوال التابعة عليه.

ما الاسم البديل للنوع (type alias) في Go؟

الاسم البديل هو اسم ثانٍ لنوع موجود أصلًا. ويُكتب باستخدام =:

example.gogo
type Handler = http.Handler

النوعان Handler و http.Handler متطابقان. يمكنك تمرير أحدهما حيث يُتوقَّع الآخر، ولا يطلب المترجم أي تحويل أبدًا، ويطبع %T الاسم الأصلي. ولا ينشئ الاسم البديل أيضًا مجموعة دوال تابعة جديدة. فالتصريح عن دالة تابعة على اسم بديل هو نفسه التصريح عنها على النوع الأصلي، لذا تنطبق عليه قاعدة «non-local type» أيضًا.

تستخدم Go الأسماء البديلة في أسمائها المُصرَّح عنها مسبقًا. فالنوع byte اسم بديل لـ uint8، و rune لـ int32، و any لـ interface{}. ولهذا السبب يدخل []byte إلى دالة تأخذ []uint8 دون تحويل.

أُضيفت الأسماء البديلة في Go 1.9 أساسًا كي ينتقل نوع بين الحزم دون أن يتعطل كود المستدعين. فحين تنقل قاعدة كود كبيرة النوع Order من billing إلى orders، تحتفظ الحزمة القديمة بالسطر type Order = orders.Order لبعض الوقت. ويستمر الكود الذي ما زال يستورد billing في الترجمة، وينتقل المستدعون واحدًا تلو الآخر (مدونة Go).

هل يمكن أن يكون الاسم البديل للنوع عامًّا (generic)؟

نعم، منذ Go 1.24. يمكن للاسم البديل أن يصرّح عن معاملات أنواع، ويجب أن يُنشأ منه نوع محدد (instantiation) حيث يُستخدم (ملاحظات إصدار Go 1.24):

example.gogo
type Set[K comparable] = map[K]struct{}

func main() {
	allowed := Set[string]{"GET": {}, "HEAD": {}}
	var plain map[string]struct{} = allowed
	_, ok := plain["GET"]
	fmt.Println(ok, len(allowed))
	fmt.Printf("%T\n", allowed)
}

يطبع هذا البرنامج true 2 ثم map[string]struct {}. فما زال Set[string] مجرد اسم لـ map[string]struct{}، لذا لا يحتاج الإسناد إلى plain إلى أي تحويل.

النوع المُعرَّف مقابل الاسم البديل للنوع: ما الفرق؟

النوع المُعرَّف (type UserID int64)الاسم البديل (type Handler = http.Handler)
نوع جديد؟نعم، مختلف عن كل نوع آخرلا، مطابق للنوع الأصلي
الخلط مع النوع الأصلييحتاج إلى تحويل صريحيعمل مباشرةً
الدوال التابعة للنوع الأصليلا تُورَثالدوال التابعة نفسها، لأنه النوع نفسه
التصريح عن دوال تابعة جديدةنعم، في الحزمة نفسهافقط إذا كان الأصلي نوعًا مُعرَّفًا في حزمتك
ما يطبعه %Tmain.UserIDالاسم الأصلي
الاستخدام المعتادالمعرّفات والوحدات والتعدادات وأنواع المجال (domain types)نقل الأنواع بين الحزم، والأسماء الأقصر

في معظم الأحيان تحتاج إلى نوع مُعرَّف. والجأ إلى الاسم البديل حين يجب أن يعني اسمان النوع نفسه تمامًا، وهذا يحدث عادةً أثناء إعادة الهيكلة (refactoring).

كيف تعمل أنواع الدوال في Go؟

توقيع الدالة نوع كأي نوع آخر، لذا يمكنك تسميته باستخدام type. وحين يصبح له اسم، يمكن أن تكون له دوال تابعة. وتستخدم المكتبة القياسية هذا لتحويل دالة عادية إلى قيمة interface. فالنوع http.Handler هو interface ذات دالة تابعة واحدة هي ServeHTTP، والدالة وحدها ليست Handler:

example.gogo
func health(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "ok")
}

var h http.Handler = health
example.texttext
./main.go:13:23: cannot use health (value of type func(w http.ResponseWriter, r *http.Request)) as http.Handler value in variable declaration: func(w http.ResponseWriter, r *http.Request) does not implement http.Handler (missing method ServeHTTP)

تصرّح الحزمة net/http عن type HandlerFunc func(ResponseWriter, *Request) وتعطيه دالة تابعة ServeHTTP تستدعي الدالة نفسها. والتحويل باستخدام http.HandlerFunc(health) ينتج قيمة تحقق الـ interface. وتُجري http.HandleFunc و mux.HandleFunc هذا التحويل نيابةً عنك (توثيق net/http). ويشرح مقال الكلمة المفتاحية func في Go قيم الدوال والـ closures والـ method values بالتفصيل.

ويعمل النمط نفسه مع سياساتك الخاصة. فسياسة إعادة المحاولة (retry policy) دالة تحوّل رقم المحاولة إلى مدة انتظار، ويمكن لدالة تابعة على نوع الدالة أن تغلّفها:

example.gogo
package main

import (
	"fmt"
	"time"
)

type RetryPolicy func(attempt int) time.Duration

func (p RetryPolicy) Capped(limit time.Duration) RetryPolicy {
	return func(attempt int) time.Duration {
		return min(p(attempt), limit)
	}
}

func Exponential(base time.Duration) RetryPolicy {
	return func(attempt int) time.Duration {
		return base << attempt
	}
}

func main() {
	policy := Exponential(100 * time.Millisecond).Capped(time.Second)
	for attempt := range 5 {
		fmt.Println(attempt, policy(attempt))
	}
}
example.texttext
0 100ms
1 200ms
2 400ms
3 800ms
4 1s

يخبر الاسم القارئ بالغرض من الدالة، واستدعاء Capped يبدو كاستدعاء دالة تابعة على أي قيمة أخرى. ويمكن لعميل HTTP أن يأخذ حقلًا من النوع RetryPolicy، ويمكن للاختبارات أن تمرّر سياسة تعيد 0 دائمًا.

كيف تعمل الأنواع العامة (generic types) في Go؟

يمكن أن يحتوي تصريح النوع على معاملات أنواع بين أقواس مربعة بعد الاسم. واستجابة API مقسّمة إلى صفحات (paginated) حالة شائعة، لأن حقول التصفح هي نفسها لكل نوع من العناصر:

example.gogo
package main

import (
	"fmt"
	"strconv"
)

type Page[T any] struct {
	Items      []T
	NextCursor string
}

func (p Page[T]) HasMore() bool {
	return p.NextCursor != ""
}

func (p Page[T]) Map[U any](fn func(T) U) Page[U] {
	out := Page[U]{NextCursor: p.NextCursor}
	for _, item := range p.Items {
		out.Items = append(out.Items, fn(item))
	}
	return out
}

type Order struct {
	ID    int64
	Total int64
}

func main() {
	orders := Page[Order]{
		Items:      []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}},
		NextCursor: "b3JkZXI6Mg",
	}
	ids := orders.Map(func(o Order) string { return strconv.FormatInt(o.ID, 10) })
	fmt.Println(ids.Items, ids.HasMore())
}

يطبع هذا البرنامج [1 2] true. فالنوعان Page[Order] و Page[string] نوعان مختلفان مبنيان من تصريح واحد. وتكرر الدوال التابعة معامل النوع في المستقبِل، كما في func (p Page[T]).

تصرّح Map عن معامل نوع خاص بها هو U. وهذه دالة تابعة عامة (generic method)، وهي جديدة في Go 1.27 (ملاحظات إصدار Go 1.27). ومع go 1.26 أو ما قبله في go.mod، يفشل الكود نفسه بالخطأ:

example.texttext
./main.go:17:22: generic method requires go1.27 or later (-lang was set to go1.26; check go.mod)

أما الدوال التابعة في الـ interface فما زال لا يمكن أن تكون لها معاملات أنواع. فالسطر Map[U any](fn func(string) U) []U داخل interface يفشل بالخطأ interface method must have no type parameters، لذا لا تُحتسب الدالة التابعة العامة أبدًا في تحقيق interface.

أين تظهر type أيضًا في Go؟

ماذا تفعل .(type) في type switch؟

لا تصلح x.(type) إلا داخل switch، وهي تتفرع بحسب النوع الديناميكي لقيمة interface. ومن الاستخدامات الشائعة الأخطاء:

example.gogo
func describe(err error) string {
	switch e := err.(type) {
	case nil:
		return "ok"
	case *fs.PathError:
		return "file error on " + e.Path
	case net.Error:
		return fmt.Sprintf("network error, timeout=%v", e.Timeout())
	default:
		return "other: " + e.Error()
	}
}

في كل حالة يكون لـ e نوع تلك الحالة، لذا يُترجم e.Path و e.Timeout() دون تأكيد نوع (type assertion) منفصل. واستدعاء describe مع الخطأ الناتج عن os.Open("/etc/levelupgo.yaml") يطبع file error on /etc/levelupgo.yaml حين لا يكون الملف موجودًا.

لا ينظر type switch إلا إلى الخطأ الخارجي. فبعد fmt.Errorf("load config: %w", err) ينتهي الاستدعاء نفسه إلى default. وللأخطاء المغلّفة، استخدم errors.As، التي تمرّ على السلسلة كلها:

example.gogo
var pathErr *fs.PathError
if errors.As(wrapped, &pathErr) {
	fmt.Println("errors.As found", pathErr.Path)
}

كيف تُستخدم type في قيود الأنواع العامة؟

القيد (constraint) هو interface يُصرَّح عنها عادةً باستخدام type، ويمكن أن تسرد أنواعًا إلى جانب الدوال التابعة. والرمز ~ أمام نوع ما يعني «أي نوع نوعه الأساسي هو هذا النوع»، وهذا ما يسمح بمرور الأنواع المُعرَّفة:

example.gogo
type Cents int64

type Amount interface {
	~int64 | ~float64
}

func Sum[T Amount](values []T) T {
	var total T
	for _, v := range values {
		total += v
	}
	return total
}

يعيد Sum([]Cents{4999, 1250}) القيمة 6249 من النوع Cents. ودون ~ يُرفض Cents، ويشير المترجم إلى الحل:

example.texttext
./main.go:20:17: Cents does not satisfy Amount (possibly missing ~ for int64 in Amount)

والتعدادات في Go مبنية على الأنواع المُعرَّفة أيضًا. فنوع مثل type OrderStatus int مع كتلة const تستخدم iota يعطيك قائمة قيم محددة النوع. ويشرح مقال الكلمة المفتاحية const في Go ذلك خطوة بخطوة، ويشرح مقال الكلمة المفتاحية var في Go كيف تبدأ متغيرات هذه الأنواع بقيمتها الصفرية.

أين يأتي دور LevelUpGo

يعلّم LevelUpGo لغة Go عبر تمارين تشغّل كود Go حقيقيًا في المتصفح. تبني دورة Composite Types الـ structs ودوال الإنشاء (constructor functions) والدوال التابعة خطوة بخطوة. وتغطي دورة Interfaces & Polymorphism الـ interfaces الضمنية وتأكيدات النوع و type switch. وتمرّ دورة Go Generics Masterclass على الأنواع العامة والقيود المخصصة. ويقدّم Training Ground تمارين قصيرة مستقلة للتدرّب خارج أي دورة. وللتعرّف على الكلمات المحجوزة الـ 24 الأخرى، راجع الكلمات المفتاحية في Go: شرح جميع الكلمات الـ 25.

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

هل type كلمة مفتاحية في Go؟

نعم. type واحدة من الكلمات المفتاحية المحجوزة الـ 25 في Go، لذا لا يمكنك استخدامها اسمًا لمتغير أو دالة. وهي تبدأ تصريحًا عن نوع، إما تعريف نوع مثل type UserID int64 أو اسمًا بديلًا مثل type Handler = http.Handler. وتظهر أيضًا في type switch على شكل x.(type).

ما الفرق بين تعريف النوع والاسم البديل للنوع في Go؟

تعريف النوع، type UserID int64، ينشئ نوعًا جديدًا مختلفًا عن int64 ويبدأ دون أي دوال تابعة. أما الاسم البديل، type ID = int64، فهو اسم آخر لـ int64 نفسه. قيم النوع المُعرَّف تحتاج إلى تحويل كي تختلط بالنوع الأصلي، بينما لا يحتاج الاسم البديل إلى أي تحويل لأن الاسمين يدلان على النوع نفسه.

هل يمكنك إضافة دوال تابعة إلى نوع مدمج في Go؟

لا. لا يمكن التصريح عن الدوال التابعة إلا على الأنواع المُعرَّفة في الحزمة نفسها، لذا يفشل func (n int) IsEven() bool بالخطأ cannot define new methods on non-local type int. عرّف نوعك الخاص، مثل type Quantity int، وصرّح عن الدالة التابعة عليه.

لماذا تقول Go "cannot use id (variable of int64 type UserID) as int64 value"؟

لأن UserID نوع مُعرَّف، ولا تحوّل Go أبدًا من تلقاء نفسها قيمة محددة النوع من نوع مُسمّى إلى آخر، حتى حين يكون النوع الأساسي هو نفسه. والثوابت غير المحددة النوع مثل 42 وحدها تتكيف مع النوع الذي تحتاجه. حوّل صراحةً باستخدام int64(id). وإذا وجدت نفسك تحوّل طوال الوقت، فالأرجح أن الدالة ينبغي أن تأخذ UserID بدلًا من ذلك.

ماذا تفعل .(type) في Go؟

هي صيغة type switch من تأكيد النوع. فالسطر switch e := err.(type) يقارن النوع الديناميكي لقيمة interface بكل case، وداخل كل حالة يكون لـ e نوع تلك الحالة. ولا تعمل إلا داخل switch. ولاختبار نوع واحد، استخدم v, ok := x.(T)، وللأخطاء المغلّفة استخدم errors.As.

المصادر

اكتب Go كما يكتبها مهندس أول

دروس تفاعلية في متصفحك. الدروس الأولى مجانية.

جرّب درسًا مجانيًاأو أنشئ حسابًا مجانيًا