Ko'pchilik tillar initsializatsiya qilinmagan o'zgaruvchiga xavf sifatida qaraydi: C o'sha xotira manzilida avvaldan qolgan tasodifiy qiymatni qoldiradi, muammoni umuman ushlaydigan tillar esa buni odatda qiymatga birinchi marta tegilganda runtime istisnosi bilan qiladi. Go butunlay boshqacha pozitsiyani egallaydi. Har bir o'zgaruvchi e'lon qilingan zahoti, butunlay o'z tipi bilan belgilanadigan aniq qiymatga initsializatsiya qilinadi.
Goda initsializatsiya qilinmagan
intdegan narsa yo'q — faqat siz hali ma'noli qiymat o'zlashtirmaganintbor.
Bu kafolat, qanchalik kichik eshitilmasin, siz ilova mantig'ining bir qatorini yozishdan oldinoq butun bir turkum xatolarni yo'q qiladi, va u idiomatik Go kodi qanday tuzilishini — shu jumladan umuman konstruktor funksiyasi yozasizmi yoki yo'qmi — belgilab beradi.
Har qanday tip uchun nol qiymat — unga xotira ajratib, har bir bitni nolga tenglashtirish natijasida hosil bo'ladigan qiymat. Ko'pchilik ichki tiplar uchun bu intuitiv "bo'sh" yoki "o'chiq" holat bilan chiroyli mos keladi:
var i int // 0
var f float64 // 0.0
var c complex128 // (0+0i)
var b bool // false
var s string // ""
Havolaga o'xshash tiplar uchun — pointer'lar, slice'lar, map'lar, channel'lar, funksiyalar va interface'lar — nol qiymat nil:
var p *int
var sl []int
var m map[string]int
var ch chan int
var fn func()
var any interface{}
Kompozit tiplar bu qoidani rekursiv tarqatadi. Nol qiymatli struct'ning har bir maydoni o'sha maydonning nol qiymatiga tenglashtiriladi; nol qiymatli massivning har bir elementi nollangan bo'ladi:
type Person struct {
Name string
Age int
}
var p Person // Person{Name: "", Age: 0}
var arr [5]int // [0, 0, 0, 0, 0]
Bularning hech biri siz har bir tip uchun yodlaydigan maxsus holat emas — hammasi to'g'ridan-to'g'ri "har bir bitni nolla" ta'rifidan kelib chiqadi, va aynan shuning uchun struct'ning nol qiymatini hosil qilish, unda qancha maydon bo'lishidan qat'i nazar, har doim arzon.
Amaliy foyda siz oddiy, e'tiborga loyiq bo'lmagan kod yozgan zahoti ko'rinadi:
var counter int
counter++ // xavfsiz — counter 0 dan boshlanadi
var name string
if name == "" { // xavfsiz — darhol solishtiriladigan va aniq belgilangan
name = "unknown"
}
Goda bu qatorlarning hech biri ikkinchi marta o'ylashni talab qilmaydi, ammo kafolatlangan nollash yo'q tildagi ekvivalenti yo ochiq initsializator, yo aniqlanmagan holatga qarshi himoya tekshiruvini talab qiladi. O'sha tekshiruvning yo'qligi Go tomonidan dangasalik emas — bu kompilyator va runtime ishni bir marta, xotira ajratish paytida bajarishi, shunda quyi oqimdagi har bir chaqiruvchi uni takrorlashi shart bo'lmaydi.
Go sizga o'zgaruvchini e'lon qilishning bir-birini qoplaydigan uchta usulini beradi, va qaysi birini tanlashingiz asosan imkoniyat emas, kontekst va urg'u masalasi — ularning hammasi bir xil natija beradi.
Tipi ochiq ko'rsatilgan var — eng batafsil shakl va paket darajasida, yoki funksiya ichida nol qiymatni ataylab xohlaganingizda hamda shu niyat ko'rinib turishini istaganingizda to'g'ri tanlov:
var x int
var name string = "Alice"
Tipi xulosa chiqariladigan var siz baribir qiymat berayotganingizda ortiqcha tip izohini tashlab yuboradi:
var x = 42 // int
var name = "Alice" // string
:=, ya'ni qisqa o'zgaruvchi e'loni — funksiya tanasida siz eng ko'p yozadigan shakl: u qisqaroq va blok boshida tabiiy o'qiladi:
x := 42
name := "Alice"
Kompilyator eslatmaguncha unutish oson bo'lgan bitta cheklov bor: := faqat funksiya tanasi ichida ishlaydi. Paket darajasida har bir e'lon var dan foydalanishi shart, chunki paket darajasidagi e'lonlar funksiyadagi ifodalar kabi ketma-ket bajarilmaydi — kompilyatorga initsializatsiya tartibini aniqlash uchun ochiq shakl kerak (bu haqda quyiroqda).
Go yana bitta ifodada bir nechta o'zgaruvchini e'lon qilish va almashtirish imkonini beradi, bu esa boshqa C oilasidagi tillar olib yuradigan butun bir turkum vaqtinchalik o'zgaruvchi shovqinini yo'q qiladi:
var x, y = 1, 2
x, y = y, x // almashtirish — vaqtinchalik o'zgaruvchisiz, avval o'ng tomon to'liq hisoblanadi
Bo'sh identifikator _ esa siz qabul qilishga majbur bo'lgan, lekin kerak bo'lmagan qiymatni tashlab yuboradi — ko'pincha ataylab tekshirmayotgan xatoni yoki bir nechta qaytariladigan qiymatdan birini:
_, err := os.Open("config.json")
count, _ := strconv.Atoi(input) // bu yerda xatoni e'tiborsiz qoldirish jiddiy xatoliklarga olib kelishi mumkin
Ikkinchi misolga namuna sifatida emas, balki ogohlantirish sifatida qarang:
strconv.Atoifunksiyasidan qaytgan xatoni tekshirmasdan tashlab yuborish noto'g'ri kiritilgan qiymat sezilmasdan0ga aylanib ketishiga sabab bo'ladi.
Goda new Person(...) kabi maxsus operator, klasslarga asoslangan konstruktorlar yoki til darajasidagi alohida konstruktor tushunchasi yo'q. Buning o'rniga idiomatik kelishuv mavjud: odatda NewX deb nomlangan, to'liq foydalanishga tayyor X tipidagi qiymatni (yoki uning manzilini) qaytaruvchi oddiy funksiya yoziladi.
type Person struct {
Name string
Age int
}
func NewPerson(name string, age int) *Person {
return &Person{Name: name, Age: age}
}
Ushbu kelishuv obyektni yaratish paytida haqiqiy mantiqiy ishlar bajarilishi zarur bo'lgandagina o'zini oqlaydi — masalan, parametrlarni tekshirish (validatsiya), boshlang'ich bufer ajratish yoki tarmoq ulanishini o'rnatish:
func NewPerson(name string, age int) (*Person, error) {
if name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
if age < 0 {
return nil, fmt.Errorf("age cannot be negative")
}
return &Person{Name: name, Age: age}, nil
}
Biroq Goda eng yaxshi amaliyot — konstruktor kerak bo'lmagan holatlarni to'g'ri anglashdir. Agar tipning nol qiymati allaqachon xavfsiz va to'liq ishlashga tayyor holat bo'lsa, alohida konstruktor yozish shunchaki ortiqcha qolip (boilerplate) hisoblanadi:
type Counter struct {
value int
}
func (c *Counter) Increment() { c.value++ }
func (c *Counter) Value() int { return c.value }
var c Counter // darhol foydalanishga tayyor, konstruktor kerak emas
c.Increment()
Standart kutubxonadagi sync.Mutex, bytes.Buffer va strings.Builder aynan shunday loyihalashtirilgan — ularning nol qiymati to'liq ishlaydi, shu sababli Go kodida hech qachon sync.NewMutex() kabi funksiyalarni uchratmaysiz.
Yangi tiplarni loyihalashtirayotganda standart kutubxona mualliflari kabi fikrlang: bu tip uchun tabiiy, xavfsiz nol holati bormi? Agar bo'lsa, unga tayaning va ortiqcha konstruktor yozmang.
Agar tip yaratilishidan oldin ma'lum shartlar bajarilishi shart bo'lsa — masalan, ma'lumotlar bazasiga ulanish, faylni ochish yoki parametrlar chegarasini belgilash — aynan shunda konstruktor va undan qaytadigan error o'z o'rnida bo'ladi.
Bir nechta ixtiyoriy sozlamaga ega tiplar uchun ikkita asosiy pattern ishlatiladi:
type PersonConfig struct {
Name string
Age int
Email string
}
func NewPerson(cfg PersonConfig) *Person {
return &Person{Name: cfg.Name, Age: cfg.Age, Email: cfg.Email}
}
type PersonOption func(*Person)
func WithEmail(email string) PersonOption {
return func(p *Person) { p.Email = email }
}
func NewPerson(name string, age int, opts ...PersonOption) *Person {
p := &Person{Name: name, Age: age}
for _, opt := range opts {
opt(p)
}
return p
}
p := NewPerson("Alice", 30, WithEmail("alice@example.com"))
Funksional parametrlarning afzalligi shundaki, kelajakda yangi parametr qo'shilganda eski chaqiruvlar buzilmaydi va orqaga moslik (backward compatibility) to'liq saqlanadi.
Struct literallari struct'ning barcha yoki ma'lum maydonlarini ochiq belgilash imkonini beradi; qolgan barcha ko'rsatilmagan maydonlar avtomatik tarzda o'z nol qiymatini oladi:
type Point struct{ X, Y int }
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1} // Y avtomatik 0 bo'ladi
p3 := Point{1, 2} // pozitsion — barcha maydonlarni tartib bo'yicha talab qiladi
var p4 Point // nol qiymat: {0, 0}
Pozitsion literallar qisqa bo'lsa-da, nozik va xavfli: struct'ga yangi maydon qo'shilsa yoki ularning o'rni almashtirilsa, boshqa fayllardagi barcha pozitsion chaqiruvlar xatolikka uchraydi. Kalitli literallar (Point{X: 1, Y: 2}) esa ancha barqaror bo'lib, har qanday jiddiy loyihalarda aynan shular tavsiya etiladi.
Slice, map va channel tiplarining nol qiymati bilan "bo'sh, ammo initsializatsiya qilingan" holati ikki xil narsa ekanligini yaxshi anglash zarur:
var s1 []int // nil — o'qish xavfsiz, len(s1) == 0, ammo hali xotira ajratilmagan
s2 := []int{} // nil emas, bo'sh slice
s3 := make([]int, 0, 10) // nil emas, bo'sh, sig'imi (capacity) 10
var m1 map[string]int // nil — undan o'qish mutlaqo xavfsiz...
v := m1["missing"] // ...v == 0 qaytadi, hech qanday xato yo'q
m1["missing"] = 1 // ...ammo nil map'ga YOZISH panic beradi! (panic: assignment to entry in nil map)
m2 := make(map[string]int) // yozishdan oldin make yoki literal orqali initsializatsiya qilish shart
Ushbu nomutanosiblik — ya'ni nil map'dan o'qish xavfsiz, lekin unga qiymat yozish runtime panic berishi — yangi Go dasturchilarini tez-tez chalg'itadigan nozik jihatdir.
Channel'lar ham xuddi shunday ishlaydi: nil channel'ga ma'lumot yuborish yoki undan o'qish hech qachon panic bermaydi, balki dasturni abadiy bloklab qo'yadi. Bu ba'zan select blokida ataylab qo'llanilsa-da, e'tiborsizlik oqibatida goroutine'larning xotirada qolib ketishiga (goroutine leak) sabab bo'lishi mumkin.
Paket darajasida e'lon qilingan var o'zgaruvchilari faylda yozilgan qat'iy tartibda emas, balki ularning o'zaro bog'liqliklar grafigi bo'yicha initsializatsiya qilinadi:
var a = b + 1 // b pastroqda yozilgan bo'lsa ham kompilyator buni to'g'ri hal qiladi
var b = 2
Paketdagi barcha o'zgaruvchilar to'liq initsializatsiya qilinganidan keyingina paketning init() funksiyalari ishga tushadi. Agar bitta paketda bir nechta init() bo'lsa, ular fayl nomlari bo'yicha tartiblanadi, biroq bir nechta init() ning ketma-ketlik tartibiga qat'iy tayanish yaxshi amaliyot emas, aksincha xavfli arxitektura (code smell) belgisidir. Va faqat barcha paketlarning initsializatsiyasi tugagach, main paketining main() funksiyasi ishga tushadi.
Ushbu aniq initsializatsiya tartibi kafolati tufayli paketlar o'z
init()funksiyalari orqali xavfsiz sozlamalarni o'rnatishi, haydovchilarni (driver) ro'yxatdan o'tkazishi yoki ma'lumotlarni tekshirib olishi mumkin.
Nol qiymatlar va deterministik initsializatsiya tartibi — Goning xavfsiz dasturlash falsafasining ajralmas qismidir. Keyingi bobda biz Goda o'zgarmas qiymatlarni ifodalovchi konstantalar va iota imkoniyatlari bilan tanishamiz.