مفاهیم پایهای Go
در زبان Go، درک تفاوت بین مقدار، متغیر، نوع داده و ساختار داده اهمیت زیادی دارد.
value یعنی خود مقدار، مثل 6 یا "علی".
variable ظرفی است که مقدار درون آن قرار میگیرد (مثل const, let, یا var).
data type نوع ظرف را مشخص میکند، مثل string یا int.
data structure مجموعهای از چند نوع داده است، مثل list, map, tree یا dictionary.
Mutable و Immutable Types
Mutable data types دادههایی هستند که با تغییر مقدارشان، مکانشان در حافظه تغییر نمیکند.
مانند:
Slice, Array, Map, Channel
Immutable data types دادههایی هستند که در صورت تغییر، حافظهی جدیدی به آنها اختصاص داده میشود:
Boolean, Int, Float, Pointers, String, Interfaces
اگر در مورد immutability اعداد شک دارید، میتوانید این لینک را مطالعه کنید:
Are ints and strings immutable in Go?
Type Casting و Conversion
Type Casting در Go وجود ندارد، اما میتوان با Conversion نوع مقدار را هنگام قرار دادن در یک متغیر متفاوت تغییر داد.
بهعبارتی، اگر مقدار از یک نوع باشد ولی متغیری که میخواهیم در آن بریزیم نوع متفاوتی داشته باشد، از conversion استفاده میکنیم.
Race Condition
اگر دو دستور بهصورت همزمان بخواهند مقدار یک متغیر را تغییر دهند، Race Condition رخ میدهد.
برای جلوگیری از آن از mutex استفاده میکنیم.
Type Embedding
به این معناست که میتوان یک نوع (مثلاً struct یا interface) را درون struct دیگر قرار داد.
در این حالت فیلد embedded بخشی از struct بیرونی محسوب میشود و برای دسترسی به آن نیازی به نامگذاری مجدد نیست.
تفاوت بین struct embedding و field containment در این است که در embedding، struct داخلی بخشی از struct بیرونی است و بهصورت مستقیم قابل دسترسی است.
در کار با JSON، structهای embedded کلید جداگانهای در خروجی JSON ندارند.
type Outer struct {
Inner // Embedded struct
}
type GetOrderInfoBodyResponse struct {
Message string `json:"message"`
ResultCode int `json:"resultCode"`
ResponseBody GetOrderInfoResponse `json:"responseBody"` // This is a field, not embedding
}Deadlock vs Blocking
زمانی که channel پر باشد و goroutineها منتظر خالی شدن آن بمانند اما هیچگاه این اتفاق نیفتد، deadlock رخ میدهد.
همچنین اگر همهی goroutineها منتظر داده باشند و هیچکدام نتوانند دادهای تولید کنند، باز هم deadlock ایجاد میشود.
در صورتی که mutex قفل شود ولی هیچ goroutineی آن را آزاد نکند، باز هم deadlock است.
اما blocking حالتی طبیعی است؛ مثلاً goroutine منتظر دادهی channel یا آزاد شدن mutex میماند.
تفاوت اصلی این است که اگر همهی goroutineها در حالت block باشند و هیچکدام نتوانند دیگری را آزاد کنند، deadlock اتفاق میافتد.
Function / Method Overloading
در Go قابلیت Overloading وجود ندارد؛ یعنی نمیتوان چند تابع با نام یکسان ولی پارامترهای مختلف داشت.
اما روشهای جایگزین وجود دارد:
- استفاده از Variadic Functions
- تعریف توابع مشابه با نامهای کمی متفاوت (مثلاً
addInt،addFloat) - استفاده از Receiverها و پیادهسازی در Interfaceها
Type Assertion
اگر بخواهیم نوع دقیق متغیری از نوع interface را مشخص کنیم:
t, ok := i.(string)در اینجا اگر i از نوع string باشد، ok برابر true خواهد بود.
Function Prototype / Signature
ترکیب نام تابع، لیست پارامترها و نوع خروجی را Function Signature میگویند.
Interface Compliance Assertion
برای اطمینان از اینکه یک struct واقعاً یک interface را پیادهسازی کرده است (در زمان کامپایل):
var _ Shape = (*Rectangle)(nil)Blank Identifier (_)
اگر تابعی چند خروجی داشته باشد و بخواهیم بعضی از آنها را نادیده بگیریم، از _ استفاده میکنیم:
_, res := Double(8)String Formatting with Placeholders
میتوان داخل رشتهها با علامت % مقادیر را جایگزین کرد (مثل fmt.Sprintf("%d", num)).
Variadic Functions
توابعی که تعداد نامحدودی ورودی میگیرند:
func functionName(params ...Type)این نوع پارامتر باید آخرین پارامتر تابع باشد. معمولاً زمانی استفاده میشود که پارامتر اختیاری باشد.
Composite Literals
زمانی که یک data structure را ایجاد و همزمان مقداردهی اولیه کنیم، از composite literal استفاده کردهایم:
arr := [3]int{1, 2, 3}
slice := []int{1, 2, 3}
bt := []byte("salam")
m := map[string]int{"Alice": 30, "Bob": 25}
gg := struct{ name string }{name: "ali"}
gg2 := struct{}{}Empty Struct
هیچ حافظهای اشغال نمیکند.
کاربردها:
- بهجای آرایه یا اسلایس برای ساخت مجموعهای از دادهها با دسترسی سریع:
map_obj := make(map[string]struct{}) - ارسال سیگنال در channelها (البته context روش بهتر است).
GoPATH و GoROOT
GoROOT مسیر نصب زبان Go و ابزارهای داخلی آن است.
GoPATH مسیر workspace کاربر و محل ذخیرهی پکیجها و باینریهاست.
export GOPATH=$HOME/go
export GOROOT=/usr/local/goنمونه مسیرها:
/home/seyed/go/pkg/mod/github.com/gin-gonic/ // Packages
/home/seyed/go/bin/swag // Compiled binaries
/usr/local/go/bin/go // Go compiler binary
/usr/local/go/src/fmt/ // Built-in packages
Go Modules Commands
-
go install
برای نصب ابزارهای CLI (بدون نیاز به go mod):go install github.com/rakyll/hey@latest -
go get
برای نصب و بهروزرسانی پکیجهای ماژول:go get github.com/segmentio/kafka-go -
go mod tidy
هماهنگسازی پکیجهای استفادهشده باgo.mod:go mod tidy -
go mod download
دانلود پکیجها ازgo.mod(بدون تغییر آن، برای build آفلاین):go mod download
Use Named Return Values
در Go میتوان نام خروجیها را در تعریف تابع مشخص کرد:
func calculate(x, y int) (sum int, product int) {
sum = x + y
product = x * y
return
}Variable Types
Visibility Across Packages
- Exported variables — با حرف بزرگ شروع میشوند و در پکیجهای دیگر قابل مشاهدهاند.
- Unexported variables — با حرف کوچک شروع میشوند و فقط در همان پکیج قابل مشاهدهاند.
Accessibility Throughout the Package
- Global variables — بیرون از تابعها تعریف میشوند و در تمام توابع پکیج قابل دسترسیاند.
در goroutineها استفاده از global variableها خطرناک است و ممکن است race condition ایجاد کند. - Local variables — درون تابع تعریف میشوند و فقط همان تابع به آنها دسترسی دارد.
Functional Options Pattern
زمانی که میخواهیم سازندهی (constructor) یک struct را بنویسیم و برخی فیلدها را اختیاری کنیم، برای هر فیلد یک متد setter تعریف میکنیم و در نهایت همهی آنها را روی struct اعمال میکنیم.
Variable Scope و Lifetime
Scope مشخص میکند که متغیر در چه محدودهای معتبر است (در سطح پکیج، تابع یا بلوک).
Lifetime مدت زمانی است که متغیر در حافظه زنده میماند.
وقتی متغیر از scope خارج شود و هیچ referenceی به آن نباشد، garbage collector آن را آزاد میکند.
متغیرهای global تا پایان اجرای برنامه باقی میمانند.
Type Comparable
structها در Go قابل مقایسهاند اگر تمام فیلدهایشان قابل مقایسه باشند.
اما function typeها قابل مقایسه نیستند.
Functional Programming در Go
هرچند Go یک زبان تمامعیار functional نیست، ولی از مفاهیم مهم آن پشتیبانی میکند:
- First-Class Functions — تابعها میتوانند در متغیرها ذخیره شوند یا بهعنوان پارامتر ارسال شوند.
در زبان گو ، فانکشن ها first class هستند
بعضی زبان های برنامه نویسی قابلیتی دارند که می توان، فانکشن را درون یک متغییر ریخت ، به قابلیت فرس کلاس فانکشن می گوند
و در کل بشه رفتار هایی که با وریبل های معمول مانند عدد یا استرینگ می توان انجام داد را با فانکشن ها نیز می توان انجام داد
- Higher Order Functions — تابعی که تابع دیگری را میگیرد یا برمیگرداند.
تابعی که بتوان یک تابع به عنوان پارامتر بگیرد ، یا بتوان خروجی اش یک تابع باشد، از این قابلیت برخوردار است
توجه : این به این معنی نیست که تابع مقدار دهی شده ( کانکریت) در ورودی یا خروجی استفاده شود ، این یعنی تایپ آن فانکشن است
همچنین توجه شود که نام تابع به عنوان ورودی یا خروجی استفاده نمی شود و تنها تعداد ورودی و تعداد خروجی آن تابع به عنوان متغییر مشخص است مثال
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello\n")
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}در این مثال، تایپ hello به عنوان ورودی به HandleFunc داده شده است.
Closure
تابعی که خروجی آن تابع دیگری است:
func makeAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}
func main() {
addFive := makeAdder(5)
result := addFive(3) // 5 + 3 = 8
fmt.Println(result)
}