Aceasta este traducerea în română a unui articol publicat inițial în engleză pe Stackademic (Medium): The Magical Apple Basket and Go Slice Mysteries.
Ai un coș cu 10 mere. Iei 5 dintre ele și le pui într-un coș mai mic. Și acum partea ciudată: cum ajungi să ai 7 mere într-un al treilea coș, dacă în al doilea ai pornit doar cu 5? Pare imposibil, dar în Go se poate. Să vedem cum.
Mai întâi facem coșul mare cu mere, adică array-ul inițial.
// Coșul inițial, cu 🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎 (un array)
firstBasketOfApples := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Luăm 5 mere din coșul mare și le punem într-un al doilea coș, mai mic. În Go, acest al doilea coș se numește „slice”.
// Al doilea coș, cu 🍎🍎🍎🍎🍎 din primul (un slice)
secondBasketOfApples := firstBasketOfApples[:5]
fmt.Printf("secondBasketOfApples it has %d elements and components: %v \n", len(secondBasketOfApples), secondBasketOfApples)
// Output:
// secondBasketOfApples it has 5 elements and components: [1 2 3 4 5]
Acum vine scamatoria. Din coșul mic, cu 5 mere, facem un al treilea coș cu 7 mere.
// Scamatoria: un slice nou cu 🍎🍎🍎🍎🍎🍎🍎
// făcut doar din secondBasketOfApples, care are 🍎🍎🍎🍎🍎
thirdBasketOfApples := secondBasketOfApples[0:7]
fmt.Printf("thirdBasketOfApples it has %d elements and components: %v \n", len(thirdBasketOfApples), thirdBasketOfApples)
// Output:
// thirdBasketOfApples it has 7 elements and components: [1 2 3 4 5 6 7]
Ca să înțelegem „magia”, ne uităm din ce e făcut un slice în Go. Are trei componente:
len): câte elemente are slice-ul;cap): până la ce lungime poate crește slice-ul fără să aibă nevoie de un array nou.Structura e definită în pachetul runtime, în fișierul slice.go:
// Structura internă a unui slice în Go
type slice struct {
array unsafe.Pointer
len int
cap int
}
Slice-ul secondBasketOfApples a păstrat un pointer către primul element al array-ului (firstBasketOfApples[0]), lungimea 5 și capacitatea 10, adică mărimea array-ului original.
Cum thirdBasketOfApples e făcut din secondBasketOfApples, își poate întinde lungimea până la capacitatea array-ului original, care e 10. De asta am putut pune două mere „în plus” în thirdBasketOfApples.
Dacă nu știi că un slice „copil” se poate întinde dincolo de lungimea slice-ului „părinte”, poți avea mai multe probleme:
secondBasketOfApples[0:11], programul se oprește cu panic. Doar append alocă un array nou atunci când capacitatea nu mai ajunge.Dacă vrei să oprești comportamentul ăsta, folosește notația [x:y:z] când creezi slice-ul nou:
// Limităm capacitatea slice-ului nou
limitedSlice := firstBasketOfApples[0:5:5]
Cu [x:y:z], capacitatea slice-ului nou e limitată la z - x, deci nu mai poți adăuga mere „în plus” din array-ul original. Notația e descrisă în specificația limbajului, la „full slice expressions”.
Dacă nu folosești [x:y:z], verifică întotdeauna lungimea și capacitatea cu len() și cap(), ca să fii sigur că nu depășesc valorile la care te aștepți.
Slice-urile din Go sunt puternice și flexibile, dar merită să știi ce se întâmplă sub capotă. Așa scrii cod Go mai sigur, mai eficient și mai ușor de întreținut.
Note adăugate la traducere; nu există în articolul original din 2023. Actualizate pentru Go 1.27, versiunea curentă la data publicării.
append pe copil scrie peste părinte. E unul dintre cele mai frecvente bug-uri legate de slice-uri. Dacă slice-ul copil mai are capacitate, append nu alocă nimic, ci scrie direct în array-ul părintelui:
parent := []int{1, 2, 3, 4, 5}
child := parent[:2]
child = append(child, 99)
fmt.Println(parent) // [1 2 99 4 5], elementul 3 a dispărut
Soluția e aceeași notație din articol, parent[:2:2], sau slices.Clip (Go 1.21), care face exact asta. Cu capacitatea tăiată, append e obligat să aloce un array nou, iar părintele rămâne neatins.
Capacitatea nu e întotdeauna mărimea array-ului. În exemplul cu merele capacitatea iese 10 pentru că slice-ul începe de la indexul 0. Regula generală: capacitatea unui slice a[low:high] e len(a) - low. De exemplu, firstBasketOfApples[3:5] are lungimea 2, dar capacitatea 7.
Un slice mic poate ține în viață un array mare. Cât timp există un slice care indică spre un array, garbage collector-ul nu poate elibera array-ul. Dacă păstrezi 10 octeți dintr-un buffer de 10 MB, ții în memorie tot bufferul. Când ai nevoie doar de o bucată mică pe termen lung, fă o copie cu slices.Clone.
Cea mai bună explicație oficială a acestor mecanisme rămâne articolul Go Slices: usage and internals de pe blogul Go.
La DO IT MAGIC SOFTWARE scriem în Go sisteme care rulează în producție, de la platforme web la soluții AI locale. Dacă ai un proiect Go care are nevoie de o a doua opinie, scrie-ne.