تعلم لغة 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 بصياغة بسيطة، وتُفحص أنواعها وقت الترجمة، وتعامل الأخطاء كقيم، وتدعم المعالجة المتزامنة بالإجراءات والقنوات، لذلك تناسب الخوادم والأعمال الكثيرة المتوازية.