01 / 73

Go nima uchun yaratilgan?

2007-yilning kuzida Google'ning uchta muhandisi — Robert Grizemer (Robert Griesemer), Rob Payk (Rob Pike) va Ken Tompson (Ken Thompson) — yirik C++ loyihalaridan birining kompilyatsiya bo'lishini (build) kutib turgan paytda doskada yangi til uchun chizmalar chiza boshladi. Bu tafsilot hikoyani qiziqarli qilish uchun to'qilgan afsona emas — voqea haqiqatan ham aynan shunday boshlangan. O'sha paytda Google infratuzilmasining asosiy qismi C++da yozilgan, Java va Pythonda yozilgan qismlar esa tobora ko'payib borardi. Uchala til ham bir xil muammo ostida qiynalardi: ular bitta kompyuter va kichik jamoalar davri uchun mo'ljallangan edi, Google'ning ulkan miqyosi esa bu doiraga sig'masdi. Yirik C++ xizmatlarini yig'ish vaqti sekin-asta ko'p daqiqalarga, ba'zan soatlarga cho'zilib ketgandi. Sababi shundaki, C++ning header fayllari va shablonlari (templates) kompilyatorni har bir fayl uchun ulkan hajmdagi kodni qaytadan tahlil qilishga majbur qilardi. Bog'liqliklar grafigi (dependency graph) shu qadar chigallashib ketgandiki, import ro'yxatiga qarab loyihaga aslida qanday kutubxonalar tortib kelinayotganini aniqlash mushkul edi. Sanoat esa aynan o'sha davrda burilish nuqtasidan o'tayotgan edi: ko'p yadroli protsessorlar odatiy holga aylangandi, ammo o'sha davrning yetakchi tillari concurrency'ni (bir vaqtda parallel bajarilishni) tilning asosiy imkoniyati emas, balki keyinchalik qo'shilgan tashqi kutubxona sifatida ko'rardi. Bu esa oqimlar (threads), qulflar (locks) va umumiy xotira atrofida juda ehtiyotkorlik bilan — va ko'pincha xatolarga to'la — mantiq qurishga majbur qilardi.

Go — Google'ning aynan o'sha vaziyatga bergan munosib javobi. U mavhum nazariyalar yoki til nafisligi bo'yicha ilmiy mashq sifatida emas, balki uning yaratuvchilari Yerdagi eng yirik taqsimlangan tizimlarni boshqarishda har kuni duch keladigan amaliy muammolarni hal qilish uchun loyihalashtirilgan. Bu amaliy ruh tilning har bir jihatida yaqqol sezilib turadi.

Go darslikda jozibali eshitiladigan, lekin amalda katta kod bazalarini o'qishni qiyinlashtiradigan, kompilyatsiyani sekinlashtiradigan yoki xato ishlatilishi oson bo'lgan murakkab imkoniyatlardan ongli ravishda voz kechadi.

Ushbu bob ana shu dizayn falsafasi haqida: Go qaysi muammolarni hal qilishni maqsad qilgan, u ataylab qanday murosalarga (trade-off) borgan va loyihangiz uchun u to'g'ri vosita ekanini qanday baholash mumkin.

Google aslida nimani tuzatmoqchi edi

Bu muammolarni aniq tushunib olish foydali, chunki ular keyinchalik tilda g'alati ko'rinadigan deyarli har bir qarorning asl sababini tushuntirib beradi.

Katta miqyosdagi kompilyatsiya tezligi. C++da kompilyatsiya vaqti kod bazasi hajmiga to'g'ri proporsional o'smaydi: shablonlarni aniqlashtirish (template instantiation) va header fayllarini qo'shish tufayli bitta faylga kiritilgan kichik o'zgarish loyihaning ulkan qismini qaytadan kompilyatsiya qilishga majbur qilishi mumkin.

Google miqyosida bu shunchaki mayda noqulaylik emas — bu har bir dasturchining ish kunidan olinadigan katta vaqt yo'qotishi edi.

Go mualliflari aniq yechimga qo'l urdi: sintaksisi sodda, importlari aniq ko'rsatilgan, preprotsessori va murakkab shablonlari bo'lmagan tilni soddaroq kompilyator ancha tezroq kompilyatsiya qila oladi. Bundan tashqari, Go paketining kompilyatsiya natijasi o'z object faylida yetarlicha ma'lumot saqlaydi, shu sababli kompilyator o'zi bog'liq bo'lgan paketlarning manba kodini qayta-qayta o'qishga majbur bo'lmaydi. Yig'ish vaqtining loyihaning umumiy hajmiga emas, balki nima o'zgarganiga qarab o'sishining siri ham shunda.

Bog'liqliklarning aniqligi. Katta C++ yoki Java tizimlarida bir bo'lak kod o'ziga aynan qaysi kutubxonalarni tortib kelishini loyihani yig'ib ko'rmaguningizcha bilish qiyin. Go har bir importni aniq ko'rsatishni talab qiladi va ishlatilmaydigan importlarni qat'iyan taqiqlaydi: kompilyator ishlatilmagan importni shunchaki ogohlantirish emas, to'g'ridan-to'g'ri xato deb hisoblaydi. Yangi boshlovchilarni hayratga soladigan bu bitta qoida bejiz kiritilmagan — Google "ehtiyot shart" deb qo'shib qo'yilgan importlar yillar davomida to'planib qolganda nima bo'lishini o'z tajribasida ko'rgan: hech kim ortiqcha qismlarni xavfsiz o'chira olmaydi va hech kim aslida nima zarurligini aniq ayta olmaydi.

Katta jamoalarda kodni o'qish qulayligi. Google muhandislari doimiy ravishda bir jamoadan boshqasiga o'tib turadi, bir jamoa yozgan infratuzilma kodini esa boshqa jamoalar muntazam o'qiydi, xatolarini tuzatadi va kengaytiradi. C++ ham, Java ham bir-biridan keskin farq qiladigan shaxsiy uslublarga keng yo'l ochadi: operatorlarni ortiqcha yuklash (operator overloading), chuqur vorislik (inheritance) ierarxiyalari, generic metaprogramlash. Go mualliflari shunday xulosaga keldi: dasturchiga bitta g'oyani ifodalashning kamroq, lekin standart usulini berish katta jamoa uchun umumiy hisobda ulkan yutuq bo'ladi — garchi bu ko'proq erkinlik va murakkab sintaktik vositalarga o'rgangan tajribali dasturchilarga dastlab biroz cheklovdek tuyulsa ham. gofmt vositasi ham aynan shu qarashdan kelib chiqqan: agar kodning formatlanishi shaxsiy did masalasi bo'lmay qolsa, kodni ko'rib chiqishdagi (code review) behuda bahslar butunlay yo'qoladi va har bir fayl yangi jamoa a'zosi uchun bir xil tushunarli uslubda o'qiladi.

Concurrency birinchi darajali imkoniyat sifatida. 2000-yillarning o'rtalariga kelib shu narsa ayon bo'ldi: protsessor unumdorligining o'sishi endi takt chastotasining tezlashuvidan emas, balki yadrolar sonining ko'payishidan keladi. O'sha davrda hukmron bo'lgan "oqim va qulf" (thread-and-lock) modelini xatosiz qo'llash naqadar mashaqqatli ekani barchaga ayon edi: mutekslar bilan himoyalangan umumiy o'zgaruvchan holat (shared mutable state) testlash va xatosini topish juda qiyin bo'lgan muammolarni keltirib chiqaradi. Goning yechimi — g'oyaviy jihatdan Toni Xoarning (Tony Hoare) 1978-yilgi "Communicating Sequential Processes" (CSP) nazariyasiga asoslangan goroutine'lar va channel'lar bo'ldi. Go bir vaqtda bajariluvchi (concurrency) arxitekturani operatsion tizim oqimlari ustiga qurilgan og'ir kutubxona sifatida emas, balki tilning ajralmas, birinchi darajali imkoniyatiga aylantirdi. Biz keyinroq bir necha bobni aynan shunga bag'ishlaymiz, chunki bu Goning zamonaviy dasturlashga qo'shgan eng buyuk hissasidir.

Qo'lda boshqarmasdan xotira xavfsizligini ta'minlash. C va C++ xotira ustidan to'liq nazorat beradi, lekin buning evaziga jiddiy xatolar turkumini keltirib chiqaradi: xotira bo'shatilgandan keyin foydalanish (use-after-free), ikki marta bo'shatish (double-free), bufer chegarasidan chiqib ketish (buffer overrun). O'tgan o'n yilliklar davomida tizimli dasturiy ta'minotda topilgan xavfsizlik zaifliklarining katta qismiga aynan shu muammolar sabab bo'lgan. Goda xotirani avtomatik tozalovchi GC (garbage collector) mavjud bo'lib, u shunchaki maksimal o'tkazuvchanlik (throughput) uchun emas, balki qisqa va oldindan bashorat qilinadigan kechikishlar uchun sozlangan. Chunki Google uchun partiyali (batch) og'ir hisob-kitoblardan ko'ra, tarmoqdagi kechikishga o'ta sezgir mikroservislar muhimroq edi.

Go amalda qanday til

Shu kelib chiqishni bilgan holda, Goni mavhum imkoniyatlar ro'yxati orqali emas, qanday dasturlar uchun yaratilgani orqali tasvirlash foydaliroq.

Go — tarmoq dasturiy ta'minotini yaratish uchun mo'ljallangan, kompilyatsiya qilinadigan, statik tiplashtirilgan va GC ga ega til. Aniqroq aytganda, bu shuni anglatadiki, u REST va gRPC API'lar, buyruq qatori (CLI) vositalari, infratuzilma va DevOps vositalari (Docker, Kubernetes, Terraform va Prometheus — hammasi Goda yozilgan, bu tasodif emas), shuningdek minglab bir vaqtdagi ulanishni qulab tushmasdan ochiq ushlab turishi kerak bo'lgan back-end xizmatlar uchun kuchli standart tanlovdir.

Goning o'z qat'iy qarashlariga ega ekani tasodif emas, u shunday loyihalashtirilgan. Kodni formatlashning bitta usuli (gofmt), bog'liqliklarni boshqarishning bitta tizimi (modullar) va aksariyat Go dasturchilari o'z-o'zidan kelib qo'shiladigan standart loyiha tuzilishi bor — garchi til buni qat'iy talab qilmasa ham.

Bu haqiqiy murosa: siz ifoda erkinligining bir qismidan voz kechasiz, buning evaziga kod bazalari o'sib borgani va mualliflari vaqt o'tishi bilan almashgani sayin ham o'qishga qulay bo'lib qolaveradi.

Go ataylab ko'pchilik zamonaviy tillarga qaraganda kichikroq qilib yaratilgan. Sakkiz yildan ortiq vaqt davomida — 2012-yil martdagi 1.0 versiyasidan tortib 2022-yil martda Go 1.18'da generic'lar paydo bo'lgunga qadar — tilga ataylab juda kam yangi konstruksiya qo'shildi.

Taklif qilingan har bir imkoniyat yuqori talabdan o'tishi kerak edi: u har bir Go dasturchisining tasavvuriga qo'shadigan qo'shimcha murakkablikni oqlaydimi?

Boshqa tillardagi ko'plab mashhur g'oyalar — implementatsiya merosi (implementation inheritance), metodlarni ortiqcha yuklash (method overloading), parametrlarning standart qiymatlari, istisnolar (exceptions) — ko'rib chiqilgan va rad etilgan. Buning sababi ularning foydasidan bexabarlik emas, balki Go jamoasining amalda bu imkoniyatlar o'zi bergan qulaylikdan ko'ra ko'proq zarar (kutilmagan xatti-harakat va sezilmaydigan xatolar ko'rinishida) keltiradi degan xulosasi edi.

Bularning hech biri Go kuchsiz degani emas. Embedding orqali kompozitsiya, interface'lar orqali strukturaviy tiplashtirish va 1.18 versiyasidan beri generic'lar Goga haqiqiy ifoda imkoniyatini beradi.

Ammo tilning asosiy qarashi shuki, biror narsani yozishning eng ko'zga tashlanadigan usuli ayni paytda idiomatik usul ham bo'lishi kerak.

Shunda tajribali va tajribasiz Go dasturchilari o'xshash muammolarga o'xshash ko'rinishdagi yechimlar bilan keladi.

Go ataylab nimalardan voz kechgan

Go qanday til bo'lishga urinmasligini bilish ham xuddi shunchalik foydali, chunki uni noto'g'ri vaziyatda qo'llashga urinish yangi boshlovchilar uchun asabiylashishning keng tarqalgan sababi.

Goda klassik meros (inheritance) yo'q. Na extends kalit so'zi, na subklass tushunchasi mavjud. Tiplar embedding (ichki biriktirish) orqali birlashtiriladi, xatti-harakat esa strukturaviy jihatdan mos keluvchi interface'lar orqali tavsiflanadi (tip o'zi qaysi interface'ni amalga oshirishini ochiq e'lon qilmaydi — u shunchaki talab qilingan metodlarni yozadi, qolganini kompilyator o'zi tekshiradi). Agar sizning "ob'ektga yo'naltirilgan dasturlash" haqidagi tasavvuringiz Java yoki C++ uslubidagi klass ierarxiyalari bo'lsa, Go dastlabki bir necha hafta notanish tuyuladi. Ko'pchilik Go dasturchilari oxir-oqibat kompozitsiyani afzal ko'rib qoladi, lekin bu moslashuv oson kechmaydi.

Go ilmiy-tadqiqot yoki eksperimental til emas. Yangi imkoniyatlar, odatda, boshqa tillarda o'zini oqlamaguncha yoki ichkarida yillar davomida ishlatilib, ochiq taklif muhokamasidan o'tmaguncha chiqarilmaydi: generic'lar tilga kirgunga qadar taxminan o'n yillik bahs va prototiplashdan o'tdi. Bu Goni konservativ tanlovga aylantiradi: hali to'liq sinovdan o'tmagan, pishmagan imkoniyatlar sabab xatoga yo'liqish ehtimoli juda kam, ammo boshqa tillarda tezda paydo bo'ladigan ayrim sintaktik qulayliklarni ancha uzoq kutishingizga to'g'ri keladi.

Go xom hisoblash unumdorligi uchun Fortran, C yoki qo'lda nozik sozlangan C++ darajasida optimallashtirilmagan, garchi u server ish yuklamalarining aksariyati uchun yetarlicha tez bo'lsa ham. U interface yoki mobil qurilmalarni birinchi o'ringa qo'yadigan platforma ham emas: gomobile va turli GUI vositalar to'plamlari mavjud bo'lsa-da, Go ekotizimining ham, Google'ning o'z sarmoyasining ham asosiy e'tibori bu yerga qaratilmagan.

Goda istisnolar (exceptions) yo'q. Xatolar oddiy qiymatlar bo'lib, funksiyaning odatiy natijalari bilan birga qaytariladi va ularni chaqiruvchi tomon aniq tekshiradi.

Bu tilning dizaynidagi eng katta oqibatlarga olib keladigan va eng ko'p bahs qo'zg'atadigan qarorlardan biri. Keyinroq biz kitobning butun bir qismini shunga bag'ishlaymiz, chunki bu Go dasturining har bir darajasida boshqaruv oqimini (control flow) qanday qurishingizni o'zgartiradi.

Go qachon to'g'ri vosita ekanini aniqlash

Dizayn maqsadlarini yodda tutgan holda, bir nechta aniq tavsiya.

Go har qanday turdagi tarmoq xizmatlari — REST API'lar, gRPC xizmatlari, xabar qabul qiluvchi xizmatlar (message consumers / queues), proksilar va yuk taqsimlagichlar (load balancers) — uchun ajoyib tanlov bo'lib chiqadi, chunki standart kutubxonadagi net/http va unga aloqador paketlar yetuk, goroutine'lar esa minglab bir vaqtdagi ulanishni boshqarishni alohida mahorat emas, tabiiy usulga aylantiradi. U buyruq qatori vositalari va dasturchi utilitalari uchun ham xuddi shunday kuchli, chunki go build maqsadli kompyuterga alohida runtime o'rnatishni talab qilmaydigan, statik bog'langan bitta ikkilik (binary) fayl hosil qiladi — siz kimgadir shu bitta faylni berasiz, u shunchaki ishlab ketaveradi. Aynan shu xususiyat Goni mikroservislar to'plami uchun tabiiy moslikka aylantiradi: yuzlab nusxani ishga tushirganingizda ikkilik faylning kichikligi, tez ishga tushishi va bo'sh turgandagi kam xotira sarfi muhim ahamiyatga ega bo'ladi.

Jamoaning asosiy ustuvorligi yangi xodimlarni tez ishga jalb qilish va tajriba darajasi har xil bo'lgan ko'plab hissa qo'shuvchilar orasida kod sifatini bir tekis saqlash bo'lsa, Go oqilona standart tanlov hisoblanadi: tilning kichikligi yangi xodimning kod bazasini o'qishni va unga ishonishni boshlashi uchun ketadigan vaqtni haqiqatan ham qisqartiradi.

Go zaifroq tanlov bo'ladigan holatlar ham bor. Muammoingizning asosiy qismi sonli yoki ilmiy hisoblashlardan iborat bo'lsa, Pythonning NumPy/SciPy/PyTorch ekotizimi yoki Julia kabi til sizni maqsadga tezroq yetkazadi. Native GUI ilova yoki mobil ilova yaratayotgan bo'lsangiz, Goning vositalari platformaning o'z yechimlari yoki Flutter kabi frameworklar oldida ancha kambag'al. Tez bajariladigan, bir martalik avtomatlashtirish uchun skript tili kerak bo'lsa esa, Goning tahrirlash-kompilyatsiya-ishga tushirish sikli qanchalik tez bo'lmasin, shell skripti yoki Pythondagi bir qatorlik koddan ko'ra ko'proq ovora qiladi.

Boshqa tildan kelayotganlar uchun

Ushbu kitob o'quvchilarining aksariyati kamida bitta boshqa tilni biladi, shuning uchun kutilmalarni tanish narsaga solishtirib to'g'rilab olish foydali.

Agar siz Pythondan kelayotgan bo'lsangiz, eng katta o'zgarish statik tiplashtirish bo'ladi: har bir o'zgaruvchi e'lon qilingan joyning o'zida — yo aniq ko'rsatish, yo xulosa chiqarish (inference) yo'li bilan — qat'iy tipga ega bo'ladi va tiplar mos kelmasligini runtime istisnosi emas, kompilyator ushlaydi. Go Python moslashuvchanligining bir qismidan voz kechadi, buning evaziga xom unumdorlikda katta sakrashga (interpretator yo'q, GIL yo'q) va CPython'ning threading'i kabi global qulf (global lock) bilan kurashmaydigan o'z concurrency yondashuviga ega bo'ladi. Python istisno ko'taradigan (raise) va uni ushlaydigan (catch) joyda Go xatoni oddiy qiymat sifatida qaytaradi:

example.py
# Python
def divide(a, b):
    return a / b  # b == 0 bo'lsa ZeroDivisionError ko'taradi
example.go
// Go
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

Agar siz Javadan kelayotgan bo'lsangiz, sizda statik tiplashtirish ham, GC ham allaqachon bor, shuning uchun o'tish osonroq kechadi. Ammo Go klassga asoslangan merosni kompozitsiya va interface talablarini bajarish (implementatsiya) bilan almashtiradi, JVM hamda JAR faylini esa bitta native ikkilik fayl bilan almashtiradi. Tekshiriladigan istisnolarning (checked exceptions) Goda umuman muqobili yo'q. Uning o'rniga xato qaytaradigan har bir funksiya muvaffaqiyatsizlik ehtimolini o'z imzosida (signature) ko'rinib turadigan qiladi.

Example.java
// Java
class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}
example.go
// Go
type Calculator struct{}

func (c Calculator) Add(a, b int) int {
    return a + b
}

Agar siz JavaScript yoki TypeScript'dan kelayotgan bo'lsangiz, Goning statik tiplashtirishi sizga kompilyatori doim yoqilgan va any orqali undan qutulib bo'lmaydigan TypeScript kabi tuyuladi. Goda na event loop, na async/await bor. Concurrency go kalit so'zi bilan ishga tushiriladigan goroutine'lar orqali boshqariladi, ularni bitta oqimli event loop emas, Go runtime'i hamkorlikdagi (cooperative) tartibda rejalashtiradi. Bundan tashqari, null ham, undefined ham yo'q: qiymati bo'lmasligi mumkin bo'lgan tiplar Goning nol qiymatlaridan (zero values) yoki aniq pointer'lardan foydalanadi, siz o'rganib qolgan optional-chaining usullari o'rnini esa nil tekshiruvlari egallaydi.

example.js
// JavaScript
async function fetchData() {
    try {
        const response = await fetch(url);
        return await response.json();
    } catch (error) {
        console.error(error);
    }
}
example.go
// Go
func fetchData(url string) (Data, error) {
    resp, err := http.Get(url)
    if err != nil {
        return Data{}, err
    }
    defer resp.Body.Close()
    var data Data
    if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
        return Data{}, err
    }
    return data, nil
}

Agar siz C yoki C++dan kelayotgan bo'lsangiz, Go sizga sezilarli soddalashtirish bo'lib tuyuladi: qo'lda xotira boshqaruvi yo'q, pointer arifmetikasi (pointer arithmetic) yo'q, header fayllari yo'q, preprotsessor yo'q, shablonlar (templates) yo'q. Siz native kompilyatsiyani va ko'pchilik ish yuklamalari uchun shunga yaqin unumdorlik darajasini saqlab qolasiz, ammo xotira ajratish (allocation) ustidan nozik nazoratdan voz kechasiz — buning evaziga xotira xavfsizligiga oid butun boshli bir turkum xatolarni yo'q qiladigan GC ga ega bo'lasiz.

example.c
/* C */
int* create_array(int size) {
    int *arr = malloc(size * sizeof(int));
    return arr;  /* chaqiruvchi free() qilishi shart */
}
example.go
// Go
func createArray(size int) []int {
    return make([]int, size)  // buni GC o'zi qaytarib oladi
}

Bu taqqoslashlarning birortasi ham g'olibni aniqlash uchun emas — ular siz allaqachon bilgan narsani oldinda turgan yangi narsaga bog'lab olishingizga yordam berish uchun. Goning dizayni bo'sh joyda paydo bo'lmagan va u qaysi og'riqni yengillatish uchun yaratilganini tushunsangiz, kitobning qolgan qismidagi tanlovlar o'zboshimchalikdek emas, aniq va izchil ustuvorliklar majmuasining natijasidek ko'rinadi: tez yig'ish, katta miqyosda ham o'qishga qulay kod, xavfsiz concurrency va butun muhandislik jamoasi barcha qoidalarini yodda saqlab, oson tushuna oladigan darajada ixcham til.

"Nega?" degan savolning javobi mana shu. Keyingi bobda kompyuteringizga Go vositalar to'plamini (toolchain) o'rnatamiz, shunda kitobning qolgan qismi quruq nazariya bo'lib qolmasdan, amalda ishga tushirib sinaydigan kodga aylanadi.