تعلم لغة Go من الصفر مع 25 مثالًا

تعلم لغة Go من الصفر مع 25 مثالًا

لغة Go (Golang) لغة طورتها Google. تتميز ببساطتها وسرعة ترجمتها ودعمها المدمج للمعالجة المتزامنة (concurrency)، لذلك تُستخدم على نطاق واسع في الخوادم والأدوات وأنظمة السحابة. تحتوي هذه المقالة على 25 مثالًا برمجيًا، جميعها تم تشغيلها باستخدام Go 1.27، وتعرض المخرجات الفعلية لكل مثال. تتدرج من الأساسيات إلى المعالجة المتزامنة ومعالجات الويب والأنواع العامة (Generics).

ما الذي تحتاجه

ثبّت Go من go.dev/dl وتحقق بالأمر `go version`. أنشئ ملف `.go` وشغّله بالأمر `go run اسم_الملف.go`.

رسوم توضيحية

توضح الرسوم التالية سير العمل وفكرة الإجراءات المتزامنة مع القنوات (رسوم توضيحية وليست لقطات شاشة حقيقية).
توضح الرسوم التالية سير العمل وفكرة الإجراءات المتزامنة مع القنوات (رسوم توضيحية وليست لقطات شاشة حقيقية).

المثال 1: مرحبًا بالعالم

أبسط برنامج في Go. يبدأ كل برنامج بالحزمة main والدالة main.

package main

import "fmt"

func main() {
	fmt.Println("สวัสดี Go")
}

**المخرجات الفعلية**

สวัสดี Go

المثال 2: المتغيرات والثوابت

يعلن `:=` المتغير ويعطيه قيمة في خطوة واحدة، و`var` يعلن النوع صراحةً، و`const` للقيم التي لا تتغير.

package main

import "fmt"

const schoolName = "โรงเรียนตัวอย่าง"

func main() {
	name := "สมชาย"
	var age int = 20
	fmt.Println(schoolName, name, age)
}

**المخرجات الفعلية**

โรงเรียนตัวอย่าง สมชาย 20

المثال 3: القيم الصفرية

المتغير المعلن دون قيمة يحصل على القيمة الصفرية لنوعه، مثل 0 أو نص فارغ أو false.

package main

import "fmt"

func main() {
	var n int
	var s string
	var b bool
	var f float64
	fmt.Printf("int=%d string=%q bool=%t float=%.1f\n", n, s, b, f)
}

**المخرجات الفعلية**

int=0 string="" bool=false float=0.0

المثال 4: الشرط if وelse

القرارات باستخدام if وelse if. لا تحتاج Go إلى أقواس حول الشرط.

package main

import "fmt"

func main() {
	score := 72
	if score >= 80 {
		fmt.Println("A")
	} else if score >= 70 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}
}

**المخرجات الفعلية**

B

المثال 5: حلقة for

في Go حلقة واحدة فقط هي for، ويمكن استخدامها للعد أو كحلقة while.

package main

import "fmt"

func main() {
	for i := 1; i <= 3; i++ {
		fmt.Println("นับ", i)
	}
	n := 0
	for n < 2 {
		n++
	}
	fmt.Println("n =", n)
}

**المخرجات الفعلية**

นับ 1
นับ 2
นับ 3
n = 2

المثال 6: switch

يقارن switch عدة حالات، ولا ينتقل تلقائيًا إلى الحالة التالية، لذلك لا حاجة إلى break.

package main

import "fmt"

func main() {
	day := "เสาร์"
	switch day {
	case "เสาร์", "อาทิตย์":
		fmt.Println("วันหยุด")
	default:
		fmt.Println("วันทำงาน")
	}
}

**المخرجات الفعلية**

วันหยุด

المثال 7: المصفوفات والشرائح

المصفوفة حجمها ثابت. أما الشريحة (slice) فتكبر باستخدام append ويمكن قطع جزء منها بالشكل s[a:b].

package main

import "fmt"

func main() {
	arr := [3]int{1, 2, 3}
	s := []int{10, 20}
	s = append(s, 30, 40)
	fmt.Println("array:", arr, "len:", len(arr))
	fmt.Println("slice:", s, "len:", len(s), "cap:", cap(s) >= 4)
	fmt.Println("ตัดช่วง:", s[1:3])
}

**المخرجات الفعلية**

array: [1 2 3] len: 3
slice: [10 20 30 40] len: 4 cap: true
ตัดช่วง: [20 30]

المثال 8: الخرائط (map)

تخزّن الخريطة أزواج المفتاح والقيمة. يتحقق الشكل `v, ok :=` من وجود المفتاح.

package main

import "fmt"

func main() {
	scores := map[string]int{"สมชาย": 85, "สมหญิง": 72}
	scores["สมศักดิ์"] = 45
	if v, ok := scores["สมชาย"]; ok {
		fmt.Println("คะแนน:", v)
	}
	delete(scores, "สมหญิง")
	fmt.Println("จำนวน:", len(scores))
}

**المخرجات الفعلية**

คะแนน: 85
จำนวน: 2

المثال 9: دوال بعدة قيم مرجعة

يمكن للدالة أن تعيد عدة قيم، وعادةً يُعاد الخطأ (error) كآخر قيمة.

package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("หารด้วยศูนย์ไม่ได้")
	}
	return a / b, nil
}

func main() {
	q, err := divide(10, 2)
	fmt.Println("ผล:", q, "error:", err)
	_, err = divide(1, 0)
	fmt.Println("error:", err)
}

**المخرجات الفعلية**

ผล: 5 error: <nil>
error: หารด้วยศูนย์ไม่ได้

المثال 10: الإغلاق (closure)

دالة تتذكر متغيرات من نطاقها الخارجي، وتفيد في العدادات والحالة الخاصة.

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
}

**المخرجات الفعلية**

1 2 3

المثال 11: البنى (struct) والدوال المرتبطة

تجمع struct عدة بيانات معًا، وتُرفق الدوال المرتبطة (methods) بذلك النوع.

package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func (s Student) Grade() string {
	if s.Score >= 80 {
		return "A"
	}
	return "B"
}

func main() {
	s := Student{Name: "สมชาย", Score: 85}
	fmt.Println(s.Name, s.Grade())
}

**المخرجات الفعلية**

สมชาย A

المثال 12: الواجهات (interface)

تحدد الواجهة مجموعة الدوال التي يجب أن يملكها النوع. أي نوع يملك هذه الدوال يحقق الواجهة تلقائيًا.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rect struct{ W, H float64 }

func (r Rect) Area() float64 { return r.W * r.H }

func main() {
	var s Shape = Rect{W: 3, H: 4}
	fmt.Printf("พื้นที่ = %.1f\n", s.Area())
}

**المخرجات الفعلية**

พื้นที่ = 12.0

المثال 13: المؤشرات (pointers)

تعطي &x عنوان المتغير، وتقرأ *p أو تغيّر القيمة عند ذلك العنوان، فتستطيع الدالة تعديل بيانات المستدعي.

package main

import "fmt"

func increment(n *int) {
	*n++
}

func main() {
	x := 5
	increment(&x)
	fmt.Println("x =", x)
}

**المخرجات الفعلية**

x = 6

المثال 14: معالجة الأخطاء

أنشئ الخطأ باستخدام fmt.Errorf مع %w، ثم تحقق باستخدام errors.Is إن كان الخطأ من نوع معروف.

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("ไม่พบข้อมูล")

func find(id int) error {
	if id != 1 {
		return fmt.Errorf("รหัส %d: %w", id, ErrNotFound)
	}
	return nil
}

func main() {
	err := find(2)
	fmt.Println(err)
	fmt.Println("ตรงกับ ErrNotFound:", errors.Is(err, ErrNotFound))
}

**المخرجات الفعلية**

รหัส 2: ไม่พบข้อมูล
ตรงกับ ErrNotFound: true

المثال 15: defer

يؤجل defer تنفيذ الاستدعاء حتى تنتهي الدالة. وهو مناسب لإغلاق الملفات وتحرير الموارد.

package main

import "fmt"

func main() {
	defer fmt.Println("ทำงานสุดท้าย")
	fmt.Println("เริ่มต้น")
	fmt.Println("ประมวลผล")
}

**المخرجات الفعلية**

เริ่มต้น
ประมวลผล
ทำงานสุดท้าย

المثال 16: panic وrecover

يوقف panic التنفيذ العادي، ولا يمكن لـ recover التقاط هذا الإيقاف إلا داخل دالة defer، فلا ينهار البرنامج كله.

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("กู้คืนจาก panic: %v", r)
		}
	}()
	return a / b, nil
}

func main() {
	fmt.Println(safeDivide(10, 2))
	fmt.Println(safeDivide(1, 0))
}

**المخرجات الفعلية**

5 <nil>
0 กู้คืนจาก panic: runtime error: integer divide by zero

المثال 17: الإجراءات المتزامنة (goroutine)

تبدأ الكلمة go عملًا يعمل بالتوازي، ويقوم sync.WaitGroup بانتظار انتهاء جميع الأعمال.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			_ = n * n
		}(i)
	}
	wg.Wait()
	fmt.Println("ทุกงานเสร็จแล้ว")
}

**المخرجات الفعلية**

ทุกงานเสร็จแล้ว

المثال 18: القنوات (channel)

تنقل القنوات القيم بين الإجراءات المتزامنة. يرسل هذا المثال قيمًا من goroutine إلى main ثم يغلق القناة.

package main

import "fmt"

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- i * 10
		}
		close(ch)
	}()
	for v := range ch {
		fmt.Println("ได้รับ", v)
	}
}

**المخرجات الفعلية**

ได้รับ 10
ได้รับ 20
ได้รับ 30

المثال 19: select ومهلة الانتظار

يستطيع select الانتظار على عدة قنوات في آن واحد. ويحدد time.After أقصى مدة انتظار.

package main

import (
	"fmt"
	"time"
)

func main() {
	result := make(chan string)
	go func() {
		time.Sleep(200 * time.Millisecond)
		result <- "เสร็จ"
	}()
	select {
	case msg := <-result:
		fmt.Println(msg)
	case <-time.After(50 * time.Millisecond):
		fmt.Println("หมดเวลา")
	}
}

**المخرجات الفعلية**

หมดเวลา

المثال 20: قفل Mutex لحماية البيانات المشتركة

يسمح sync.Mutex لعدة إجراءات متزامنة بتعديل متغير واحد بأمان، فيكون المجموع دائمًا صحيحًا.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	total := 0
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("รวม =", total)
}

**المخرجات الفعلية**

รวม = 100
توضح الرسوم التالية سير العمل وفكرة الإجراءات المتزامنة مع القنوات (رسوم توضيحية وليست لقطات شاشة حقيقية).
توضح الرسوم التالية سير العمل وفكرة الإجراءات المتزامنة مع القنوات (رسوم توضيحية وليست لقطات شاشة حقيقية).

المثال 21: حزمة strings والـ rune

توفر حزمة strings دوال للنصوص. تحسب len عدد البايتات، لذلك تُحوَّل النصوص التايلاندية إلى []rune لعدّ حروفها.

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Go เขียนง่าย"
	fmt.Println(strings.ToUpper("go"), strings.Contains(s, "เขียน"))
	fmt.Println(strings.Split("a,b,c", ","), len("สวัสดี"))
	fmt.Println([]rune("สวัสดี")[0] == 'ส')
}

**المخرجات الفعلية**

GO true
[a b c] 18
true

المثال 22: قراءة الملفات وكتابتها

تكتب os.WriteFile وتقرأ os.ReadFile الملف كاملًا باستدعاء واحد.

package main

import (
	"fmt"
	"os"
)

func main() {
	if err := os.WriteFile("note.txt", []byte("บันทึกจาก Go\n"), 0644); err != nil {
		fmt.Println("เขียนไม่ได้:", err)
		return
	}
	data, err := os.ReadFile("note.txt")
	if err != nil {
		fmt.Println("อ่านไม่ได้:", err)
		return
	}
	fmt.Print("อ่านได้: ", string(data))
}

**المخرجات الفعلية**

อ่านได้: บันทึกจาก Go

المثال 23: JSON

تحوّل حزمة encoding/json الـ struct إلى JSON والعكس، وتحدد وسوم `json:"..."` أسماء الحقول.

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	b, _ := json.Marshal(Student{Name: "สมชาย", Score: 85})
	fmt.Println(string(b))

	var s Student
	_ = json.Unmarshal([]byte(`{"name":"สมหญิง","score":72}`), &s)
	fmt.Println(s.Name, s.Score)
}

**المخرجات الفعلية**

{"name":"สมชาย","score":85}
สมหญิง 72

المثال 24: اختبار معالج HTTP

تتيح حزمة httptest اختبار دالة المعالجة دون تشغيل خادم حقيقي.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "สวัสดีจาก Go HTTP")
}

func main() {
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	hello(rec, req)
	body, _ := io.ReadAll(rec.Result().Body)
	fmt.Println(rec.Code, string(body))
}

**المخرجات الفعلية**

200 สวัสดีจาก Go HTTP

المثال 25: الأنواع العامة (Generics)

دوال تعمل على عدة أنواع باستخدام معامل نوع T، مع تقييده بواجهة.

package main

import "fmt"

type Number interface {
	~int | ~float64
}

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

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))
	fmt.Println(Sum([]float64{1.5, 2.5}))
}

**المخرجات الفعلية**

6
4

الخطوات التالية

جرّب كتابة خادم ويب يستقبل JSON ويحفظه في ملف. ثم ادرس حزمتي net/http وcontext لإدارة المهل والإلغاء.

الخلاصة

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