Goda klasslar ham, meros ham, konstruktorlar ham yo'q, va shunga qaramay aksariyat jiddiy Go dasturlari ma'lumotlarni bir joyga jamlab, ularga ma'no beruvchi bir necha yaxshi tanlangan tip atrofida tashkil etiladi. Bu guruhlash mexanizmi — struct. Struct — har birining o'z tipi bo'lgan, yagona tip nomi ostida birlashtirilgan nomlangan maydonlar ketma-ketligi. Bu ataylab oddiy vosita — yashirin xatti-harakat yo'q, metaklass mashinerisi yo'q, orqangizda hech narsa sodir bo'lmaydi — va aynan shu soddaligi struct'larni haqiqiy sohalarni modellashtirish uchun shunchalik samarali qiladi: buyurtmalar, foydalanuvchilar, HTTP so'rovlari, konfiguratsiya daraxtlari, tahlil daraxti tugunlari. Boshqa tilda klassni faqat ma'lumot saqlash uchun ishlatgan bo'lsangiz, Go struct'i sizga darhol tanish tuyuladi; farqi shundaki, Go ma'lumot va xatti-harakatni aniqroq ajratib turadi: xatti-harakat keyinroq metodlar orqali (keyingi boblarda yoritiladi) biriktiriladi, tip ta'rifining ichiga o'rnatilmaydi.
Struct — har birining o'z tipi bo'lgan, yagona tip nomi ostida birlashtirilgan nomlangan maydonlar ketma-ketligi.
Struct tipi o'z maydonlarini sanab o'tadi, har birining nomi va tipi bor:
type Person struct {
Name string
Age int
Email string
}
Har qanday boshqa Go identifikatorida bo'lgani kabi, bosh harf ko'rinuvchanlikni boshqaradi: Name, Age va Email bosh harf bilan boshlanadi, demak ular eksport qilinadi — Person e'lon qilingan paketdan tashqaridagi kodga ko'rinadi. Agar ular name, age, email deb nomlanganda, faqat e'lon qiluvchi paket ichida ishlatilishi mumkin bo'lardi. Godagi struct maydonlariga kirishni boshqarish hikoyasi shundan iborat; private, protected yoki public kalit so'zi yo'q, faqat birinchi harfning registri bor.
Go sizga Person qiymatini hosil qilishning bir necha usulini beradi, va qaysi birini tanlash o'zingiz qanchalik aniq aytmoqchi ekaningizga bog'liq bo'lishi kerak.
Nol qiymat hech narsa qilmasangiz ham ishlaydi:
var p Person // Person{Name: "", Age: 0, Email: ""}
Har bir maydon o'z tipining nol qiymatini oladi — string uchun bo'sh satr, int uchun 0, pointer'lar, slice'lar va map'lar uchun nil. Bu tasodifiy qulaylik emas; Go mualliflari ochiq aytgan dizayn maqsadi shundaki, "nol qiymat foydali bo'lishi kerak". sync.Mutex initsializatsiyasiz qulflashga tayyor; bytes.Buffer yozishga tayyor. O'z struct'laringizni loyihalashtirayotganda, ularning nol qiymati ham shunday foydalanishga yaroqlimi, yoki chaqiruvchilar siz oldini olishingiz mumkin bo'lgan initsializatsiya qadamini eslab qolishga majbur bo'ladimi, deb so'rash o'zini oqlaydi.
Goning dizayn maqsadi: "nol qiymat foydali bo'lishi kerak".
Kalitli struct literali o'rnatayotgan maydonlaringizni istalgan tartibda nomlaydi va qolganini nol qiymatlarida qoldiradi:
p := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}
partial := Person{Name: "Alice"} // Age va Email nol qiymatlarida qoladi
Sukut bo'yicha aynan shu shaklni ishlatishingiz kerak. Go shuningdek, maydon nomlarisiz barcha maydonlarni e'lon tartibida beradigan pozitsion literalga ham ruxsat beradi:
p := Person{"Alice", 30, "alice@example.com"}
go vet boshqa paketlardagi pozitsion struct literallarini aynan mo'rtligi uchun belgilaydi: struct ta'rifiga maydon qo'shsangiz, olib tashlasangiz yoki tartibini o'zgartirsangiz, har bir pozitsion literal jimgina noto'g'ri qiymatlarni noto'g'ri maydonlarga bera boshlaydi, va sizni ogohlantiradigan kompilyator xatosi bo'lmaydi. Kalitli literallar bu buzilish usulidan himoyalangan, shuning uchun idiomatik Go pozitsion literallarni faqat kichik, barqaror struct'lar — nuqtalar, RGB ranglar — uchun qoldiradi, bunda maydonlar tartibi amalda tipning identifikatsiyasining bir qismi hisoblanadi.
Maydonlar nuqta operatori bilan o'qiladi va yoziladi, struct'larga pointer'lar esa maydonga murojaat qilinadigan har qanday joyda avtomatik dereferens qilinadi:
p := &Person{Name: "Alice", Age: 30}
p.Age = 31 // (*p).Age = 31 ning qisqartmasi
fmt.Println(p.Name) // "Alice"
Bu avtomatik dereferens amalda muhim: aynan shu sababli *Person ni uzatib yurib, hamma joyda (*p).Name o'rniga p.Name yozishda davom eta olasiz, va metodlardagi pointer receiver'lar sintaktik yuk sifatida sezilmasligining katta qismi ham shunda.
Struct maydonlari o'zlari ham struct bo'lishi mumkin — Go oddiyroq tiplardan boyroq tiplarni shunday quradi:
type Address struct {
Street, City, Zip string
}
type Person struct {
Name string
Age int
Address Address
}
p := Person{
Name: "Alice",
Age: 30,
Address: Address{Street: "123 Main St", City: "New York", Zip: "10001"},
}
fmt.Println(p.Address.City) // "New York"
Bu oddiy kompozitsiya — Personda Address bor — va u ichma-ich yozuvlarga ega har qanday tildan kutganingizdek ishlaydi. Go buning ustiga yana bir vosita qo'shadi: faqat tip bilan, nomsiz e'lon qilingan maydon embedded (o'rnatilgan) maydon bo'ladi, va uning maydonlari (hamda metodlari, ularni kiritganingizdan keyin) tashqi struct'ga ko'tarilib o'tadi (promoted):
type Person struct {
Name string
Age int
Address // embedded — maydon nomi yo'q
}
p := Person{
Name: "Alice",
Address: Address{Street: "123 Main St", City: "New York"},
}
fmt.Println(p.City) // ko'tarilgan — City xuddi Person'ning o'z maydonidek o'qiladi
fmt.Println(p.Address.City) // aniq yo'l ham ishlayveradi
Go 1.27 dan boshlab kompozit literalning maydon kalitlari yagona yuqori darajadagi maydon nomidan ko'proq narsa bo'lishi mumkin — ular har qanday yaroqli maydon selektori bo'la oladi, ya'ni o'rnatilgan tip uchun literalni aniq ichma-ich yozish o'rniga, tashqi literaldan turib embedded maydon ichiga kirib, uning o'z maydonlaridan birini to'g'ridan-to'g'ri o'rnatishingiz mumkin:
p := Person{
Name: "Alice",
Address.City: "New York", // embedded maydon orqali kiradi
}
Bu avvalgi misollardagidek Address: Address{City: "New York"} yozishga ekvivalent, lekin embedded tuzilmaning bir necha daraja ichkarisidagi bir-ikki maydonni o'rnatishgina kerak bo'lganda va shunga yetib borish uchun oraliq literalni yozib o'tirishni xohlamaganda tabiiyroq o'qiladi. Bu embedding nimani anglatishini o'zgartirmaydi — u hamon oddiy kompozitsiya, meros emas — u shunchaki kompozit literallarga embedding allaqachon ochib beradigan narsaga murojaat qilishning to'g'ridan-to'g'riroq usulini beradi.
Embedding merosga (inheritance) sintaksisi o'xshashligi va ko'tarilgan maydonlar meros bo'lib o'tgandek tuyulishi sababli oson chalg'itadi, lekin uning ishlash mexanizmi klass ierarxiyasiga mutlaqo o'xshamaydi. Dinamik dispetcherlash yo'q, super yo'q, tiplar tizimida is-a munosabati yo'q — Address ni embedding qilgan Person kompilyatorning tip tekshiruvi nuqtai nazaridan Address emas: Address kutilgan joyga Person ni uzata olmaysiz. Sizga oddiy kompozitsiya orqali maydon va metodlarni ko'tarish (method promotion) beriladi xolos. Tashqi tip ko'tarilgan metodlarga ega bo'ladi va buning natijasida tegishli interfeys talablarini bajarishi (interface'ni implementatsiya qilishi) mumkin, ammo amal bajarilayotgan obyekt hamon o'sha ichki embedded maydon bo'lib qolaveradi.
Embedding — meros (inheritance) emas, balki qulay kompozitsiyadir:
Addressni o'z ichiga olganPersonkompilyator uchun baribirAddressemas.
Amalga oshirilishni qayta ishlatmoqchi bo'lsangiz va uning to'liq API'sini ochib qo'yishga qarshi bo'lmasangiz, embedding'ga murojaat qiling; bu API'ni tipingiz chaqiruvchilariga sizdirmasdan kompozitsiya kerak bo'lsa, nomlangan maydonga murojaat qiling.
Ba'zan qiymatga shakl kerak, lekin nom kerak emas. Go struct tipini joyida yozishga imkon beradi:
data := struct {
Status string `json:"status"`
Count int `json:"count"`
}{
Status: "ok",
Count: 42,
}
Anonim struct'lar ko'pincha testlarda (jadvalli test holatlari uchun vaqtinchalik fixture sifatida), nomlangan tip faqat bir marta ishlatiladigan bir martalik JSON dekodlashda va bir xil fayl ichida ishlatilib tashlanadigan yordamchi funksiyalarning yengil qaytariladigan tiplari sifatida uchraydi. Ular qulaylik, o'z-o'zicha dizayn andozasi emas — agar shakl bir necha funksiya yoki faylda qayta ishlatilsa, unga nom bering.
Go struct'lari, agar har bir maydon o'zi taqqoslanadigan bo'lsa, == va != ni bepul qo'llab-quvvatlaydi:
type Point struct{ X, Y int }
p1, p2, p3 := Point{1, 2}, Point{1, 2}, Point{2, 3}
fmt.Println(p1 == p2) // true — har bir maydon teng
fmt.Println(p1 == p3) // false
Sonlar, satrlar, mantiqiy qiymatlar, pointer'lar (manzil bo'yicha taqqoslanadi, ishora qilingan qiymat bo'yicha emas), taqqoslanadigan elementli massivlar va butunlay taqqoslanadigan maydonlardan tashkil topgan struct'lar — hammasi taqqoslanadigan. Slice'lar, map'lar va funksiyalar esa taqqoslanmaydi, va hatto bitta shunday maydonga ega struct taqqoslanuvchanligini butunlay yo'qotadi — faqat o'sha maydon uchun emas, butun struct'dagi == uchun, bu kompilyatsiya xatosiga aylanadi:
type Data struct {
Values []int // slice'lar hech qachon taqqoslanmaydi
}
d1, d2 := Data{Values: []int{1, 2}}, Data{Values: []int{1, 2}}
// d1 == d2 // kompilyatsiya xatosi: yaroqsiz amal ([]int bo'lgan struct'ni taqqoslab bo'lmaydi)
Shunday tipda tenglik kerak bo'lganda, uni aniq yozing yoki testlarda chuqur strukturaviy taqqoslash uchun reflect.DeepEqual yoki github.com/google/go-cmp/cmp ga tayaning — ikkinchisi testdagi tasdiqlashlar uchun amaldagi standart, chunki u muvaffaqiyatsizlikda shunchaki "teng emas" o'rniga o'qiladigan farqni (diff) chiqaradi (uni standart kutubxonaning cmp paketi bilan adashtirmang: u faqat cmp.Compare kabi tartiblash taqqoslashlarini beradi, strukturaviy farqlarni emas):
func (d Data) Equal(other Data) bool {
if len(d.Values) != len(other.Values) {
return false
}
for i, v := range d.Values {
if v != other.Values[i] {
return false
}
}
return true
}
Taqqoslanuvchanlik tipni map kaliti sifatida ishlatish imkonini ham beradi, bu esa struct o'zgaruvchan yozuv emas, identifikator — koordinata, kompozit kalit, kesh kaliti — bo'lishi kerak bo'lganda uni yodda tutishning yana bir sababi.
Struct tegi (tag) — maydonga biriktirilgan xom satr literali bo'lib, odatda bo'shliq bilan ajratilgan key:"value" juftliklarini saqlaydi va ish vaqtida qiziqqan har qanday paket tomonidan reflection orqali o'qiladi:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"` // nol qiymat bo'lsa, bu maydonni tashlab ketish
Private string `json:"-"` // bu maydonni hech qachon kodlamaslik
}
encoding/json — birinchi uchratadigan teg iste'molchisi, lekin mexanizm umumiy: encoding/xml xuddi shu g'oyani xml:"..." bilan ishlatadi, ORM'lar va so'rov quruvchilari ko'pincha o'zlarining db:"..." kelishuvini belgilaydi, tekshiruv kutubxonalari esa validate:"required,min=3" kabi teglarni belgilaydi. Bularning birortasi til imkoniyati emas — kompilyator teg satri nimani anglatishini bilmaydi ham, qiziqmaydi ham. Bu reflection'ga asoslangan kutubxonalar kelishilgan sof kelishuv, va uni ichkaridan anglash foydali, chunki u ham kuchni (istalgan kutubxona o'z teg nomlar fazosini belgilashi mumkin va bir maydonda bir nechtasi birga yashashi mumkin), ham xavfni (teg satri ichidagi imlo xatosi kompilyatorga ko'rinmaydi va faqat jimgina ish vaqti xatti-harakati o'zgarishi sifatida namoyon bo'ladi, shuning uchun go vetning struct teg tekshiruvlari va staticcheckni ishga tushirish foydali) tushuntiradi.
Struct'lar haqiqiy shaklga ega narsani — HTTP API'ning so'rov va javob tanalarini, konfiguratsiya faylini, daraxt yoki graf — modellashtirishda o'z o'rnini oqlaydi. Bitta realistik misol o'yinchoq struct'lar ro'yxatidan ko'ra ko'proq gapni tushuntiradi:
package main
import (
"encoding/json"
"fmt"
"time"
)
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusPaid OrderStatus = "paid"
OrderStatusShipped OrderStatus = "shipped"
OrderStatusDelivered OrderStatus = "delivered"
OrderStatusCancelled OrderStatus = "cancelled"
)
type Address struct {
Street string `json:"street"`
City string `json:"city"`
State string `json:"state"`
PostalCode string `json:"postal_code"`
Country string `json:"country"`
}
type OrderItem struct {
ProductID int `json:"product_id"`
ProductName string `json:"product_name"`
Quantity int `json:"quantity"`
UnitPrice float64 `json:"unit_price"`
}
type Order struct {
ID string `json:"id"`
CustomerID int `json:"customer_id"`
Items []OrderItem `json:"items"`
Status OrderStatus `json:"status"`
Shipping Address `json:"shipping"`
Billing Address `json:"billing"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (o *Order) Total() float64 {
var total float64
for _, item := range o.Items {
total += item.UnitPrice * float64(item.Quantity)
}
return total
}
func (o *Order) AddItem(item OrderItem) {
o.Items = append(o.Items, item)
o.UpdatedAt = time.Now()
}
func main() {
order := Order{
ID: "ORD-12345",
CustomerID: 1001,
Status: OrderStatusPending,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Shipping: Address{Street: "123 Main St", City: "San Francisco", State: "CA", PostalCode: "94102", Country: "USA"},
}
order.Billing = order.Shipping
order.AddItem(OrderItem{ProductID: 1, ProductName: "Go Programming Book", Quantity: 2, UnitPrice: 29.99})
order.AddItem(OrderItem{ProductID: 2, ProductName: "Go T-Shirt", Quantity: 1, UnitPrice: 19.99})
order.Status = OrderStatusPaid
data, _ := json.MarshalIndent(order, "", " ")
fmt.Println(string(data))
fmt.Printf("Total: $%.2f\n", order.Total())
}
Bu misolning bir nechta jihati alohida ta'kidlash foydali, chunki ular deyarli har bir haqiqiy Go ma'lumotlar modelida uchraydi. OrderStatus oddiy string maydon emas, alohida nomlangan tip ostidagi string — bu tip xavfsizligini beradi (OrderStatus kutadigan funksiyaga tasodifan ixtiyoriy satrni uzatib bo'lmaydi) va ish vaqtida hech qanday xarajatsiz, chunki asosiy tasvir bir xil. Address maydonlarini takrorlash o'rniga Shipping va Billing uchun qayta ishlatilgan — arzimas narsa, lekin bu xatoni bitta joyda tuzatish bilan ikkita joyda tuzatish orasidagi farq. Va metodlar, Total va AddItem, *Orderni qabul qiladigan tasodifan erkin suzuvchi funksiyalar emas, ular ishlaydigan tip yonida yashaydi — bu Goning inkapsulyatsiya o'rnini bosuvchisi: kirish modifikatorlari emas, balki tipning invariantlarini (bu yerda "buyurtmaning Totali uning Itemsini aks ettirishi kerak") ularni saqlashi kerak bo'lgan kod bilan yonma-yon joylashtirish.
Bir necha odat yillar davomida barqaror xizmat qiluvchi toza struct'larni chalkash struct'lardan ajratib turadi:
json:"name"). Aniq teglash maydon nomlari o'zgarganda tarmoq protokoli va API formatining buzilishidan saqlaydi.Address Address) qilib qoldiring.sync.Mutex yoki bytes.Buffer kabi). Bu qo'shimcha initsializatsiya bosqichlarini unutib qoldirish xavfini yo'qotadi.Ma'lumotlar tuzilishi bilan tanishib bo'lgach, navbat ularni qayta ishlovchi funksiyalar va metodlarga keladi. 19-bobda biz Go funksiyalarining chuqur imkoniyatlari — ko'p qiymatli qaytish, nomlangan qaytish qiymatlari va variadic (o'zgaruvchan sonli) parametrlar mexanikasini o'rganamiz.