مقارنة struct و slice و map في Go: تحليل شامل

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

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

ابدأ الدورة

مقارنة struct و slice و map في Go: من المبتدئ إلى المحترف

\n\n

هل سبق لك أن كتبت كودًا بلغة Go كان من المفترض أن يقارن بين هيكلين (struct)، أو شريحتين (slice)، أو خريطتين (map)، وحصلت على خطأ في الترجمة: invalid operation: cannot compare؟ إذا كان الأمر كذلك، فأنت لست وحدك. هذا أحد أكثر الأسئلة شيوعًا على Stack Overflow (عشرات الآلاف من المشاهدات!)، والذي يحير حتى المطورين ذوي الخبرة الذين انتقلوا إلى Go من لغات أخرى.

\n\n

في هذه المقالة، سنوضح مرة واحدة وإلى الأبد كيفية مقارنة أنواع البيانات المركبة في Go بشكل صحيح. ستتعرف على سبب عدم عمل == دائمًا، ومتى تحتاج إلى استخدام reflect.DeepEqual، وكيفية كتابة دالة مقارنة فعالة خاصة بك. هيا بنا!

\n\n

لماذا لا تعمل المقارنة البسيطة باستخدام == دائمًا؟

\n\n

في Go، يعمل العامل == فقط مع الأنواع القابلة للمقارنة (comparable). وتشمل هذه:

\n\n
    \n
  • الأنواع الأساسية: int، string، bool، float، إلخ.
  • \n
  • المؤشرات (pointer)
  • \n
  • القنوات (channel)
  • \n
  • الواجهات (إذا كانت الأنواع الديناميكية قابلة للمقارنة)
  • \n
  • الهياكل (struct) التي جميع حقولها من الأنواع القابلة للمقارنة
  • \n
  • المصفوفات (array)، إذا كان عنصر المصفوفة قابلاً للمقارنة
  • \n
\n\n

أما الشرائح (slice) والخرائط (map) فهي غير قابلة للمقارنة بشكل مباشر باستخدام ==. محاولة القيام بذلك ستؤدي إلى خطأ في الترجمة. لماذا؟ لأن الشريحة هي نوع مرجعي، يتضمن مؤشرًا للبيانات، والطول، والسعة. قد تكون شريحتان "متساويتين" في المحتوى، لكن لهما بنى داخلية مختلفة.

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    a := []int{1, 2, 3}\n    b := []int{1, 2, 3}\n    \n    // خطأ في الترجمة!\n    // fmt.Println(a == b) \n    \n    fmt.Println("يعمل فقط مع nil:")\n    fmt.Println(a == nil) // false\n    fmt.Println(b == nil) // false\n}
\n\n

نفس الشيء ينطبق على الخرائط. الشيء الوحيد الذي يمكنك التحقق منه باستخدام == هو المساواة مع nil.

\n\n

مقارنة الهياكل (struct) في Go

\n\n

مقارنة الهياكل هي الحالة الأكثر صعوبة. إذا كانت جميع حقول هيكلك من الأنواع القابلة للمقارنة، فإن == يعمل!

\n\n
type Person struct {\n    Name string\n    Age  int\n}\n\nfunc main() {\n    p1 := Person{"Alice", 30}\n    p2 := Person{"Alice", 30}\n    \n    fmt.Println(p1 == p2) // true — يعمل!\n}
\n\n

لكن المشكلة تأتي عندما يحتوي الهيكل على شريحة أو خريطة:

\n\n
type PersonWithHobbies struct {\n    Name    string\n    Age     int\n    Hobbies []string  // شريحة — نوع غير قابل للمقارنة\n}\n\nfunc main() {\n    p1 := PersonWithHobbies{"Bob", 25, []string{"reading", "coding"}}\n    p2 := PersonWithHobbies{"Bob", 25, []string{"reading", "coding"}}\n    \n    // خطأ في الترجمة!\n    // fmt.Println(p1 == p2)\n}
\n\n

في هذه الحالة، سيعطي مترجم Go خطأ: struct containing []string cannot be compared.

\n\n

الحل رقم 1: reflect.DeepEqual — شامل لكن بطيء

\n\n

أسهل طريقة لمقارنة أي أنواع معقدة هي استخدام الدالة reflect.DeepEqual من المكتبة القياسية للغة Go. وهي تجتاز بشكل متكرر الهياكل والشرائح والخرائط وتقارن محتوياتها.

\n\n
import (\n    "fmt"\n    "reflect"\n)\n\nfunc main() {\n    // مقارنة الشرائح\n    a := []int{1, 2, 3}\n    b := []int{1, 2, 3}\n    fmt.Println(reflect.DeepEqual(a, b)) // true\n    \n    // مقارنة الخرائط\n    m1 := map[string]int{"a": 1, "b": 2}\n    m2 := map[string]int{"a": 1, "b": 2}\n    fmt.Println(reflect.DeepEqual(m1, m2)) // true\n    \n    // مقارنة الهياكل ذات الحقول غير القابلة للمقارنة\n    p1 := PersonWithHobbies{"Bob", 25, []string{"reading"}}\n    p2 := PersonWithHobbies{"Bob", 25, []string{"reading"}}\n    fmt.Println(reflect.DeepEqual(p1, p2)) // true\n}
\n\n

<

المدونات

توصيات الكتب