Har bir tilning o'z shakli bor va uni siz o'sha tilda ishlaydigan dastur yozib ko'rgandagina haqiqiy his qilasiz — sintaksis haqida qancha o'qimang, bu hissiyotning o'rnini bosa olmaydi. Ushbu bob — o'sha shakl bo'ylab tez yurish. Biz bir nechta kichik va tugallangan dastur yozamiz, ularni ishga tushiramiz, va har biri keyingi g'oyani tanishtiradi. Bu yerdagi hech narsa yodlash uchun mo'ljallanmagan — maqsad ustalik emas, yo'nalish olish. Biz tegib o'tadigan har bir mavzu keyinroq o'ziga alohida bob oladi, u yerda sekinlashib, chekka holatlar va murosalarni ko'rib chiqamiz. Hozircha maqsad — shunchaki Go yozishga va uning ishlashini kuzatishga ko'nikish.
Har bir Go dasturi bir xil tarzda boshlanadi:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Buni hello.go nomi bilan saqlang va go run hello.go bilan ishga tushiring. To'rt qator, ammo har biri haqiqiy ish bajaryapti. package main kompilyatorga bu fayl main nomli paketga tegishli ekanini aytadi, main esa maxsus: main funksiyasi bor main paketi dasturni boshqa kod import qiladigan oddiy kutubxona emas, bajariladigan dasturga aylantiradi. import "fmt" standart kutubxonaning formatlash paketini olib keladi, u chop etish, o'qish va satrlarni formatlash bilan shug'ullanadi. func main() — kirish nuqtasi, ya'ni ishga tushadigan birinchi (va bu qadar kichik dasturda yagona) funksiya. fmt.Println esa o'z argumentlarini standart chiqishga yozadi, ular orasiga bo'sh joy va oxiriga yangi qator belgisini qo'shadi.
Shu oxirgi tafsilot — bo'sh joy va yangi qator qo'shish yoki qo'shmaslikni Go siz uchun hal qilishi — til bo'ylab sezadigan kattaroq mavzuning kichik namunasi.
Go sizga biror narsani bajarishning o'nlab sozlanadigan usulini emas, yaxshi o'ylangan bitta usulini berishga moyil.
fmt.Print, fmt.Println va fmt.Printf sizga kerak bo'ladigan deyarli barcha chop etish ehtiyojlarini qoplaydi, va ular orasidagi farqni bilib olgach (ajratuvchi va yangi qator yo'q, ikkalasi ham bor, yoki format satri), bu haqda deyarli o'ylamay qo'yasiz.
Go statik tiplashtirilgan: har bir o'zgaruvchining tipi kompilyatsiya paytida qat'iy belgilanadi, va kompilyator satrni butun songa qo'shishga uringan dasturni chiqarishdan oldin sizni to'xtatadi. Ammo Goning tip xulosasi (type inference) tufayli har bir tipni qo'lda yozib chiqish shart emas.
package main
import "fmt"
func main() {
var name string = "Alice" // tipi aniq ko'rsatilgan
var age = 30 // int deb xulosa chiqarilgan
city := "New York" // qisqa e'lon, string deb xulosa chiqarilgan
x, y := 10, 20 // bir nechta qisqa e'lon
fmt.Println(name, age, city, x, y)
}
var name string = "Alice" va city := "New York" deyarli bir xil ishni bajaradi, ammo ular turli joylarda ishlatiladi: var paket darajasida ham, funksiya ichida ham ishlaydi, := — qisqa o'zgaruvchi e'loni esa faqat funksiya ichida va faqat kamida bitta yangi o'zgaruvchi e'lon qilayotganingizda ishlaydi. Amalda idiomatik Go funksiya tanasida ko'proq := ga tayanadi, var ni esa paket darajasidagi e'lonlar, aniqlik uchun tipni ochiq ko'rsatmoqchi bo'lgan hollar, yoki nol qiymatdan boshlanib keyinroq to'ldiriladigan o'zgaruvchi uchun saqlab qo'yadi.
Shu oxirgi holat Goda ko'pchilik tillarga qaraganda muhimroq, chunki har bir tipning nol qiymati bor va Go doim o'shanga initsializatsiya qiladi.
Goda initsializatsiya qilinmagan, tasodifiy qoldiq qiymatli o'zgaruvchi degan narsa yo'q.
Sonli tiplar 0 ga, satrlar "" ga, mantiqiy tiplar false ga, pointer'lar, slice'lar, map'lar, channel'lar va interface'lar esa nil ga tenglashadi. Aynan shu sababdan var count int o'z holicha to'liq va xavfsiz ifoda: siz boshqacha demaguningizcha count — 0.
Goning asosiy tiplari aniq oilalarga bo'linadi. Ishorali va ishorasiz butun sonlar aniq kengliklarda keladi (int8 dan int64 gacha, uint8 dan uint64 gacha), ularga qo'shimcha mashina so'zi o'lchamidagi int va uint bor — aniq o'lcham kerakligiga alohida sabab bo'lmasa, standart holda o'shalarni tanlang. float32 va float64 butun bo'lmagan sonlar bilan ishlaydi, odatdagi tanlov — float64. bool — true yoki false, boshqa tiplardan yashirin o'tkazish yo'q: Godagi if hech qachon 0 yoki bo'sh satrni indamay "yolg'on" deb qabul qilmaydi. byte — uint8 ning muqobil nomi (alias), rune esa bitta Unicode kod nuqtasi (belgisi)ni ifodalovchi int32 ning muqobil nomidir (alias); ikkalasi ham satrlar bilan ishlay boshlaganingizda ahamiyat kasb etadi, bunga tez orada qaytamiz. Tip tizimining chekka holatlariga — o'tkazishlar, to'lib ketish xatti-harakati va nega Go hatto turli kenglikdagi ikki butun son tipi orasida ham aniq o'tkazishni talab qilishiga — to'liq 9-bobni bag'ishlaymiz.
Konstantalar kompilyatsiya paytida hisoblanadi va dastur ishlashi davomida hech qachon o'zgarmaydi:
const pi = 3.14159
const greeting = "Hello"
const (
StatusOK = 200
StatusNotFound = 404
)
Yuqoridagi HTTP uslubidagi holat kodlari kabi o'zaro bog'liq konstantalarni bitta blokka guruhlash idiomatik hisoblanadi va paketda ikki-uchtadan ko'p konstanta to'planganda yaxshi o'qiladi. Goda yana iota bor — o'sib boruvchi konstanta qiymatlarini qo'lda yozmasdan hosil qilish mexanizmi, u sanab o'tiladigan qiymatlar (enum) uchun qulay. Uni 11-bobda batafsil ko'rib chiqamiz.
Funksiyalar func bilan e'lon qilinadi, va Goning bir nechta qiymat qaytarish odati — ko'pincha natija bilan birga xato — deyarli darhol ko'zga tashlanadi:
package main
import "fmt"
func greet(name string) {
fmt.Println("Hello,", name)
}
func add(a, b int) int {
return a + b
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
greet("Alice")
fmt.Println("Sum:", add(5, 3))
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
divide — Goda siz deyarli har qanday boshqa shakldan ko'ra ko'proq yozadigan ko'rinish. Tilda istisnolar yo'q — hech narsa sizning ortingizdan otilmaydi va ushlanmaydi — shuning uchun muvaffaqiyatsiz bo'lishi mumkin bo'lgan funksiya oxirgi qiymat sifatida error qaytaradi, chaqiruvchi esa qolgan qaytarilgan qiymatlarga ishonishdan oldin uni tekshirishi kutiladi.
Dastlabki bir necha marta bu ortiqcha marosimdek tuyuladi, keyin refleksga aylanadi, va oxir-oqibat siz funksiya chaqiruvi ko'rinmas tarzda muvaffaqiyatsiz bo'la oladigan tillarga biroz shubha bilan qaray boshlaysiz.
19-bob funksiya dizayniga, 33-bob esa xatolarni boshqarishga ancha chuqurroq kiradi.
Goning boshqaruv oqimi konstruksiyalari ataylab kam, va ularning har biri C oilasidagi tillar talab qiladigan shart atrofidagi qavslarni tashlab yuboradi:
x := 10
if x > 5 {
fmt.Println("x is greater than 5")
} else if x == 5 {
fmt.Println("x equals 5")
} else {
fmt.Println("x is less than 5")
}
// if va uning ichidagi boshlang'ich ifoda: faqat if/else ichida ko'rinadi
if err := doSomething(); err != nil {
fmt.Println("Error:", err)
}
day := "Monday"
switch day {
case "Monday":
fmt.Println("Start of work week")
case "Friday":
fmt.Println("TGIF!")
case "Saturday", "Sunday":
fmt.Println("Weekend!")
default:
fmt.Println("Midweek")
}
E'tibor bering: C, C++, Java yoki JavaScript'dan farqli o'laroq, switch holatlari standart holda keyingi holatga o'tib ketmaydi — har bir holat yashirin tarzda uziladi, kerak bo'lsa (kamdan-kam) fallthrough kalit so'zi bilan ataylab o'tishni yoqasiz. Yuqoridagi "Saturday", "Sunday" kabi bitta holatda bir nechta qiymat sanash imkoniyati bilan birga, Godagi switch ham klassik ko'p tarmoqli shoxlanish, ham uzun if/else if zanjirlariga toza muqobil sifatida ikkiyoqlama vazifa bajaradi.
Sikllar borasida Go g'ayrioddiy va, o'rganib qolganingizdan keyin, tiniqlashtiruvchi pozitsiyani egalladi: sikl uchun aynan bitta kalit so'z bor — for, va u boshqa tillar for, while va do-while orasiga taqsimlagan barcha sikl shakllarini qoplaydi:
for i := 0; i < 5; i++ { // klassik uch qismli sikl
fmt.Println(i)
}
i := 0
for i < 5 { // while uslubidagi sikl
fmt.Println(i)
i++
}
for { // cheksiz sikl
fmt.Println("Loop forever")
break
}
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers { // slice bo'ylab yurish
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
ages := map[string]int{"Alice": 30, "Bob": 25}
for name, age := range ages { // map bo'ylab yurish (tartibsiz!)
fmt.Printf("%s is %d years old\n", name, age)
}
Oxirgi misol boshidanoq o'zlashtirib olish foydali bo'lgan ogohlantirishni o'z ichiga oladi.
Goda map bo'ylab yurish tartibi ishga tushirishlar orasida ataylab tasodifiylashtiriladi. Agar kodingiz map elementlarini muayyan tartibda ko'rishga bog'liq bo'lsa, kalitlarni o'zingiz saralashingiz kerak bo'ladi.
Bunga map'lar va sikllar haqidagi boblarda qaytamiz.
Go qat'iy o'lchamli massivlar bilan dinamik o'lchamli slice'larni farqlaydi, va amalda siz slice'larni doimo, massivlarni esa kamdan-kam ishlatasiz:
var arr [5]int // massiv: o'lcham tipning bir qismi
arr[0], arr[1] = 1, 2
arr2 := [5]int{1, 2, 3, 4, 5}
var slice []int // slice: biror narsa qo'shilmaguncha nil
slice = append(slice, 1, 2, 3)
slice2 := []int{1, 2, 3, 4, 5}
slice3 := arr2[1:4] // massivni kesish [2, 3, 4] ni beradi
Slice — kichik sarlavha: pointer, uzunlik va sig'im — u ostidagi massivga ishora qiladi. Aynan shuning uchun kesish juda tez (resurs talab qilmaydi), va aynan shuning uchun ikkita slice bir xil xotira sohasiga havola qilishi mumkin. Massivlar ko'pincha slice'lar ishora qiladigan tayanch xotira sifatida, yoki qat'iy o'lcham cheklov emas, balki afzallik bo'lgan unumdorlikka sezgir kodda uchraydi. append va kesishning birgalikdagi xotiraga murojaat (aliasing) xususiyatlariga to'liq 15-bobni bag'ishlaymiz.
Go satrlari — o'zgarmas, UTF-8 da kodlangan bayt ketma-ketliklari:
str := "Hello, World!"
length := len(str) // 13 — bayt uzunligi, belgi soni emas
greeting := "Hello" + ", " + "World"
name, age := "Alice", 30
message := fmt.Sprintf("%s is %d years old", name, age)
text := `This is a
multi-line
string`
len(str) baytlarni sanaydi, bu ASCII matn uchun belgi soniga mos keladi, lekin ko'p baytli UTF-8 ketma-ketliklari bor satrlar uchun mos kelmaydi — satr bo'ylab for i, r := range str bilan yurish esa sizga rune'larni (dekodlangan Unicode kod nuqtalarini) ularning to'g'ri bayt siljishlarida beradi. Ko'p qatorli misoldagi kabi teskari apostrof bilan o'ralgan xom satr literallari qochish ketma-ketliklarini umuman qayta ishlamaydi, bu ularni muntazam ifodalar, SQL yoki Go buzishini istamagan har qanday matn uchun qulay qiladi. 16-bob bayt/rune farqini hamda strings va unicode/utf8 paketlarini chuqur yoritadi.
Maplar — Goning ichiga o'rnatilgan xesh jadvali, va map'dan o'qishning ikki qiymatli shakli "kalit yo'q edi" bilan "kalit bor edi, lekin qiymati nol" ni farqlash usulidir:
ages := map[string]int{"Alice": 30, "Bob": 25}
ages["Charlie"] = 28
age, exists := ages["David"]
if exists {
fmt.Println("David's age:", age)
} else {
fmt.Println("David not found")
}
delete(ages, "Bob")
, exists qismini tashlab ketsangiz, ages["David"] shunchaki 0 ni beradi — ikkinchi qiymatsiz buni haqiqiy nol yoshdan ajratib bo'lmaydi. Bu haqiqiy noaniqlikni hal qiladigan kichik sintaksis bo'lagi, va odatga aylangach, siz undan doimo foydalanasiz.
Struct'lar o'zaro bog'liq maydonlarni bitta tipga guruhlaydi, metodlar esa receiver'ni e'lon qilish orqali tiplarga xatti-harakat biriktiradi:
type Person struct {
Name string
Age int
City string
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func main() {
person := Person{Name: "Alice", Age: 30, City: "New York"}
fmt.Println(person.Name)
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("Area:", rect.Area())
rect.Scale(2)
fmt.Println("Scaled area:", rect.Area())
}
Area Rectangle ni qiymat bo'yicha oladi — u faqat maydonlarni o'qiydi, shuning uchun nusxa yetarli va, ehtimol, xavfsizroq. Scale esa *Rectangle, ya'ni pointer'ni oladi, chunki u aslini o'zgartirishi kerak. Qiymat va pointer receiver orasida tanlash — Goda duch keladigan birinchi haqiqiy dizayn savollaridan biri, va 21-bob qoidalar hamda keng tarqalgan xatolarni batafsil ko'rib chiqadi.
Goda meros yo'q — klasslar yo'q,
extendsyo'q — ammo struct'lar boshqa struct'larni o'z ichiga joylashtirib (embedding), ularning maydon va metodlarini yuqoriga chiqarishi mumkin, bu esa boshqa tillarda meros ishlatiladigan holatlarning ko'pchiligini chuqur klass ierarxiyalariga xos mo'rtlikka yo'l qo'ymay qoplaydi.
Pointer qiymatning o'zini emas, uning xotiradagi manzilini saqlaydi:
x := 10
p := &x // p x ga ishora qiladi
fmt.Println(*p) // dereferens: 10 chiqaradi
*p = 20 // pointer orqali yozish
fmt.Println(x) // x endi 20
Goda pointer'lar bor, lekin pointer arifmetikasi yo'q — C'dagi kabi xotira bo'ylab yurish uchun pointer'ga butun son qo'sha olmaysiz. Bu xotira xavfsizligiga oid butun bir turkum xatolarni yo'q qiladi, ammo pointer'larning chindan foydali qismlarini saqlab qoladi: ma'lumotni nusxalamasdan bo'lishish va o'zgartirish, hamda funksiyaga chaqiruvchining o'zgaruvchisini o'zgartirish imkonini berish. 23-bob pointer'larni chuqur yoritadi, 22-bob esa ular ostidagi xotira modelini — stek/uyum (heap) farqini va Goning escape tahlili qiymat aslida qayerda yashashini qanday hal qilishini ko'rib chiqadi.
Interface xatti-harakatni tavsiflaydi — tipda bo'lishi kerak bo'lgan metodlar to'plamini — va tipning o'zi aslida nima ekani haqida hech narsa demaydi:
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func printArea(s Shape) {
fmt.Println("Area:", s.Area())
}
func main() {
printArea(Rectangle{Width: 10, Height: 5}) // Shape interface'iga mos keladi
printArea(Circle{Radius: 5}) // Shape interface'iga mos keladi
}
Na Rectangle, na Circle o'z kodida Shape ni amalga oshirishini ochiq e'lon qilmaydi — implements kalit so'zi Goda mavjud emas. Tip interface'da talab qilingan barcha metodlarga ega bo'lsa kifoya — u avtomatik ravishda shu interface'ni qanoatlantiradi (implementatsiya qiladi). Bu xususiyat strukturaviy tiplashtirish (structural typing) deb ataladi.
Bu Go dasturlash tiliga o'ziga xos qulaylik beradigan muhim xususiyatdir: interface'lar odatda ularni qabul qiladigan joyda e'lon qilinadi, juda ixcham bo'ladi (ko'pincha atigi bir yoki ikkita metod) va aniq tiplar tomonidan avtomatik tarzda bajariladi.
26- va 27-boblar interface dizayniga, shu jumladan klassik Go idiomasi "interface'larni qabul qil, struct'larni qaytar" tamoyiliga bag'ishlangan.
Biz bu shaklni yuqoridagi divide da ko'rdik; mana o'sha patternning biroz hayotiyroq sharoitdagi to'liq ko'rinishi:
import (
"fmt"
"os"
)
func readFile(filename string) (string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return string(data), nil
}
func main() {
content, err := readFile("example.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Content:", content)
}
error — bu shunchaki bitta Error() string metodi bor interface, ya'ni Godagi xatolar siz tekshirishingiz, o'rashingiz, solishtirishingiz va boshqa hamma narsa kabi uzatishingiz mumkin bo'lgan oddiy qiymatlar. O'rganish uchun try/catch yo'q — muvaffaqiyatsiz bo'lishi mumkin bo'lgan chaqiruvdan darhol keyin if err != nil ni tekshirish deyarli butun patternni tashkil qiladi — ammo xatolarni qanday qilib yaxshi yaratish, o'rash va tasniflashda haqiqiy chuqurlik bor, 33- va 34-boblar aynan shunga bag'ishlangan.
Goning konkurentlik vositalari tilning bir qismi, keyinchalik ustiga yamalgan kutubxona emas, va ularning ta'mi hatto shu qadar tez sayohatga ham kiradi:
import (
"fmt"
"time"
)
func sayHello(name string) {
for i := 0; i < 3; i++ {
fmt.Println("Hello from", name)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sayHello("Alice")
go sayHello("Bob")
time.Sleep(1 * time.Second) // qo'pol usul, shunchaki goroutine'lar tugashi uchun
}
go sayHello("Alice") sayHello ni goroutine sifatida ishga tushiradi — bu dasturning qolgan qismi bilan bir vaqtda ishlaydigan funksiya bo'lib, uni operatsion tizim oqimlariga birma-bir bog'lash emas, Go runtime'i hamkorlikda (cooperative) rejalashtiradi. Goroutine'lar shu qadar arzonki, dasturlar muntazam ravishda minglab goroutine ishlatadi. Ular orasidagi muvofiqlashtirish (yuqoridagi time.Sleep — bu vaqtinchalik o'rinbosar, haqiqiy sinxronizatsiya strategiyasi emas) channel'lar yordamida amalga oshiriladi:
func sendNumbers(ch chan int) {
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func main() {
ch := make(chan int)
go sendNumbers(ch)
for num := range ch {
fmt.Println("Received:", num)
}
}
Channel — goroutine'lar orasida qiymat uzatish uchun tiplashtirilgan kanal (channel) bo'lib, channel bo'ylab range bilan yurish u yopilgunga qadar qiymatlarni navbati bilan qabul qiladi. Concurrency Goda juda muhim va asosiy mavzu bo'lgani sababli, 40-bobdan boshlab unga beshta to'liq bob ajratiladi. Ularda xotira modeli kafolatlari, worker pool (ishchi goroutine'lar guruhi), fan-in/fan-out va pipeline kabi klassik andozalar batafsil yoritiladi.
package main
import (
"fmt"
"strings"
)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}
func filterAdults(people []Person) []Person {
var adults []Person
for _, person := range people {
if person.Age >= 18 {
adults = append(adults, person)
}
}
return adults
}
func main() {
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 15},
{Name: "Charlie", Age: 25},
}
var names []string
for _, person := range filterAdults(people) {
names = append(names, person.Name)
}
fmt.Println("Adults:", strings.Join(names, ", "))
}
Buni sekin ko'zdan kechirib chiqsangiz, ushbu bobda o'rganilgan asosiy tushunchalarni bitta kodda topasiz: struct, metod, append bilan to'ldiriladigan slice, range sikli va standart kutubxona funksiyasi (strings.Join). Kichik va tushunarli bo'laklarning bunday uyg'unligi haqiqiy Go kodiga xos eng muhim xususiyatdir.
Go yechimning eng oddiy, tushunarli va ochiq-oydin variantini yozishni rag'batlantiradi, chunki sodda variant har doim jamoaning boshqa a'zolari — va hatto bir necha oydan keyingi o'zingiz uchun ham o'qish hamda qo'llab-quvvatlash eng oson bo'lgan koddir.
Bu qisqa sayohat tilning umumiy manzarasi bilan tanishtirdi. Endi esa asboblar to'plami bilan amalda ishlashni mustahkamlash uchun har kuni ishlatiladigan asosiy buyruqlarni ko'rib chiqamiz: go run, go build, go test va go doc.