Merge

Versiunea actuală a paginii nu a fost încă examinată de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită pe 4 septembrie 2022; verificările necesită 4 modificări .
Merge
Clasa de limba multithreaded , imperativ , structurat , orientat pe obiecte [1] [2]
Tipul de execuție compilate
Aparut in 10 noiembrie 2009
Autor Robert Grismer , Rob Pike și Ken Thompson
Dezvoltator Google , Rob Pike , Ken Thompson , The Go Authors [d] și Robert Grismer [d]
Extensie de fișier .go
Eliberare
Tip sistem strict , static , cu inferență de tip
A fost influențat C [4] , Oberon-2 , Limbo , Oberon activ , Teoria interacțiunii proceselor secvențiale , Pascal [4] , Oberon [4] , Smalltalk [5] , Newsqueak [d] [6] , Modula-2 [6] , Alef [d] , APL [7] , BCPL , Modula și Occam
Licență BSD
Site-ul web go.dev
OS DragonFly BSD , FreeBSD , Linux , macOS , NetBSD , OpenBSD , Plan 9 , Solaris , Microsoft Windows , iOS , Android , AIX și Illumos
 Fișiere media la Wikimedia Commons

Go (deseori și golang ) este un limbaj de programare multi- thread compilat dezvoltat intern de Google [8] . Dezvoltarea Go a început în septembrie 2007, cu Robert Grismer , Rob Pike și Ken Thompson [9] , care au lucrat anterior la proiectul de dezvoltare a sistemului de operare Inferno , direct implicați în proiectarea acestuia . Limba a fost introdusă oficial în noiembrie 2009 . În acest moment, suportul pentru compilatorul oficial dezvoltat de creatorii limbajului este oferit pentru sistemele de operare FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX . [10] . Go este suportat și de setul de compilatori gcc și există mai multe implementări independente. O a doua versiune a limbajului este în curs de dezvoltare.

Titlu

Numele limbajului ales de Google este aproape același cu numele limbajului de programare Go! , creat de F. Gee. McCabe și C. L. Clark în 2003 [11] . Numele este discutat pe pagina Go [11] .

Pe pagina de pornire a limbii și, în general, în publicațiile de pe Internet, denumirea alternativă „golang” este adesea folosită.

Scop, ideologie

Limbajul Go a fost dezvoltat ca un limbaj de programare pentru crearea de programe extrem de eficiente care rulează pe sisteme moderne distribuite și procesoare multi-core. Poate fi văzută ca o încercare de a crea un înlocuitor pentru limbajele C și C++ , ținând cont de tehnologiile informatice schimbate și de experiența acumulată în dezvoltarea de sisteme mari [12] . În cuvintele lui Rob Pike [12] , „Go a fost conceput pentru a rezolva problemele de dezvoltare software din viața reală la Google”. El enumeră următoarele ca principale probleme:

Principalele cerințe pentru limbă au fost [13] :

Go a fost creat cu așteptarea ca programele de pe acesta să fie traduse în cod obiect și executate direct fără a necesita o mașină virtuală , așa că unul dintre criteriile de alegere a soluțiilor arhitecturale a fost capacitatea de a asigura o compilare rapidă în cod obiect eficient și absența excesivă. cerințe pentru suport dinamic.

Rezultatul a fost un limbaj „care nu a reprezentat o descoperire, dar a fost totuși un instrument excelent pentru dezvoltarea unor proiecte software mari” [12] .

Deși este disponibil un interpret pentru Go , practic nu este mare nevoie de el, deoarece viteza de compilare este suficient de rapidă pentru a permite dezvoltarea interactivă.

Principalele caracteristici ale limbii

Principalele caracteristici ale limbajului Go [9] :

Go nu include multe dintre caracteristicile sintactice populare disponibile în alte limbaje moderne de programare a aplicațiilor. În multe cazuri, acest lucru este cauzat de o decizie conștientă a dezvoltatorilor. Scurte justificări ale deciziilor de proiectare alese pot fi găsite în „Întrebările frecvente” [9] privind limbajul, mai detaliat – în articolele și discuțiile publicate pe site-ul limbajului, având în vedere diverse opțiuni de proiectare. În special:

Sintaxă

Sintaxa limbajului Go este similară cu cea a limbajului C , cu elemente împrumutate din Oberon și limbaje de scripting .

Alfabetul

Go este un limbaj sensibil la majuscule și minuscule, cu suport complet Unicode pentru șiruri și identificatori.

În mod tradițional, un identificator poate fi orice secvență nevidă de litere, numere și un caracter de subliniere care începe cu o literă și nu se potrivește cu niciunul dintre cuvintele cheie Go. „Litere” se referă la toate caracterele Unicode care se încadrează în categoriile „Lu” (litere mari), „Ll” (litere mici), „Lt” (litere majuscule), „Lm” (litere modificatoare) sau „Lo” ( alte litere), sub „numere” - toate caracterele din categoria „Nd” (cifre, cifre zecimale). Astfel, nimic nu împiedică utilizarea chirilicului în identificatori, de exemplu.

Identificatorii care diferă numai prin majuscule sunt distincte. Limba are o serie de convenții pentru utilizarea literelor mari și mici. În special, numai litere mici sunt folosite în numele pachetelor. Toate cuvintele cheie Go sunt scrise cu litere mici. Variabilele care încep cu litere mari sunt exportabile (publice), iar cele care încep cu litere mici sunt neexportabile (private).

Literale șiruri pot folosi toate caracterele Unicode fără restricții. Șirurile sunt reprezentate ca secvențe de caractere UTF-8 .

Pachete

Any Go program include unul sau mai multe pachete. Pachetul căruia îi aparține un fișier cu cod sursă este dat de descrierea pachetului de la începutul fișierului. Numele pachetelor au aceleași restricții ca și identificatorii, dar pot conține doar litere mici. Sistemul de pachete goroutine are o structură arborescentă similară cu un arbore de directoare. Orice obiect global (variabile, tipuri, interfețe, funcții, metode, elemente de structuri și interfețe) sunt disponibile fără restricții în pachetul în care sunt declarate. Obiectele globale ale căror nume încep cu o literă majusculă sunt exportabile.

Pentru a utiliza obiecte exportate de un alt pachet într-un fișier de cod Go, pachetul trebuie importat folosind import.

pachet principal /* Import */ import ( "fmt" // Pachetul standard pentru ieșirea formatată "database/sql" // Import pachetul imbricat cu "os" // Import cu alias . "math" // Import fără calificare când se utilizează _ "gopkg.in/goracle.v2" // Pachetul nu are referințe explicite în cod ) func main () { for _ , arg := interval w . Args { // Accesarea matricei Args declarată în pachetul „os” prin aliasul fmt . Println ( arg ) // Apelarea funcției Println() declarată în pachetul „fmt” cu numele pachetului } var db * sql . db = sql . Open ( driver , dataSource ) // Numele din pachetul imbricat sunt calificate // numai prin numele pachetului în sine (sql) x := Sin ( 1.0 ) // apel math.Sin() - calificare prin numele pachetului math nu este necesar // deoarece a fost importat fără nume // Nu există nicio referință la pachetul „goracle.v2” în cod, dar va fi importat. }

Listează căile către pachetele importate din directorul src din arborele sursă, a cărui poziție este dată de variabila de mediu GOPATH, în timp ce pentru pachetele standard, trebuie doar să specificați numele. Un șir care identifică un pachet poate fi precedat de un alias, caz în care va fi folosit în cod în locul numelui pachetului. Obiectele importate sunt disponibile în fișierul care le importă cu o calificare completă precum „ пакет.Объект”. Dacă un pachet este importat cu un punct în loc de un alias, atunci toate numele pe care le exportă vor fi disponibile fără calificare. Această caracteristică este utilizată de unele utilitare de sistem, dar utilizarea ei de către programator nu este recomandată, deoarece calificarea explicită oferă protecție împotriva coliziunilor de nume și a modificărilor „imperceptibile” ale comportamentului codului. Nu este posibil să importați fără calificare două pachete care exportă același nume.

Importul pachetelor în Go este strâns controlat: dacă un pachet este importat de un modul, atunci cel puțin un nume exportat de acel pachet trebuie utilizat în codul acelui modul. Compilatorul Go tratează importarea unui pachet neutilizat ca pe o eroare; o astfel de soluție obligă dezvoltatorul să țină în permanență la zi listele de import. Acest lucru nu creează dificultăți, deoarece instrumentele de asistență pentru programare Go (editore, IDE-uri) oferă de obicei verificarea și actualizarea automată a listelor de import.

Când un pachet conține cod care este folosit doar prin introspecție , există o problemă: importarea unui astfel de pachet este necesară pentru a-l include în program, dar nu va fi permisă de compilator, deoarece nu este accesat direct. _Importul anonim este furnizat pentru astfel de cazuri: „ ” (singura subliniere) este specificat ca un alias ; un pachet importat în acest mod va fi compilat și inclus în program dacă nu este menționat în mod explicit în cod. Cu toate acestea, un astfel de pachet nu poate fi utilizat în mod explicit; acest lucru împiedică controlul importului să fie ocolit prin importul tuturor pachetelor ca anonime.

Un program Go executabil trebuie să conțină un pachet numit main, care trebuie să conțină o funcție main()fără parametri și o valoare returnată. Funcția main.main()este „corpul programului” - codul său este rulat când programul pornește. Orice pachet poate conține o funcție init() - aceasta va fi rulată când programul este încărcat, înainte de a începe executarea, înainte de a apela orice funcție din acest pachet și în orice pachet care îl importă. Pachetul principal este întotdeauna inițializat ultimul și toate inițializările sunt făcute înainte ca funcția să înceapă execuția main.main().

Module

Sistemul de ambalare Go a fost conceput cu presupunerea că întregul ecosistem de dezvoltare există ca un singur arbore de fișiere care conține versiuni actualizate ale tuturor pachetelor, iar atunci când apar versiuni noi, acesta este complet recompilat. Pentru programarea aplicațiilor folosind biblioteci terțe, aceasta este o limitare destul de puternică. În realitate, există adesea restricții asupra versiunilor de pachete utilizate de unul sau altul cod, precum și situații în care versiuni (ramuri) diferite ale unui proiect folosesc versiuni diferite de pachete de bibliotecă.

Începând cu versiunea 1.11, Go acceptă așa-numitele module . Un modul este un pachet descris special care conține informații despre versiunea sa. Când un modul este importat, versiunea care a fost folosită este fixată. Acest lucru permite sistemului de compilare să controleze dacă toate dependențele sunt satisfăcute, să actualizeze automat modulele importate atunci când autorul le face modificări compatibile și să blocheze actualizările versiunilor necompatibile cu versiunea anterioară. Modulele ar trebui să fie o soluție (sau o soluție mult mai ușoară) la problema managementului dependenței.

Comentarii și punct și virgulă

Go utilizează ambele tipuri de comentarii în stil C: comentarii inline (începând cu // ...) și comentarii bloc (/* ... */). Un comentariu de linie este tratat de compilator ca o linie nouă. Bloc, situat pe o linie - ca un spațiu, pe mai multe linii - ca o nouă linie.

Punctul și virgulă din Go este folosit ca separator obligatoriu în unele operațiuni (dacă, pentru, comută). Formal, ar trebui, de asemenea, să încheie fiecare comandă, dar în practică nu este nevoie să puneți un astfel de punct și virgulă la sfârșitul liniei, deoarece compilatorul însuși adaugă punct și virgulă la sfârșitul fiecărei linii, excluzând caracterele goale, la sfârșitul liniei. identificator, număr, un caracter literal, un șir, întrerupere, continuare, fallthrough, returnare cuvinte cheie, o comandă de creștere sau decrementare (++ sau --) sau o paranteză de închidere, pătrată sau acoladă (o excepție importantă este aceea că virgula nu este inclusă în lista de mai sus). De aici rezultă două lucruri:

  • În practică, un punct și virgulă este necesar doar în unele formate de instrucțiuni if, for, switch și pentru a separa comenzile situate pe aceeași linie. Prin urmare, există foarte puține puncte și virgulă în codul Go.
  • Un efect secundar al semicolonizării automate de către compilator este că nu oriunde în program unde este permis un spațiu, puteți folosi o ruptură de linie. În special, în descrieri, comenzi de inițializare și dacă, pentru constructii de comutare, nu puteți muta acolada de deschidere pe următoarea linie:
func g () // ! { // GREȘIT } dacă x { } // ! altfel { // GREȘIT } func g (){ // DREAPTA } if x { } else { // TRUE } Aici, în primele două cazuri, compilatorul va insera un punct și virgulă în linia marcată cu un comentariu cu semn de exclamare, întrucât linia se termină (ignorând spații și, respectiv, comentarii), cu paranteze de închidere rotunde și ondulate. Ca urmare, sintaxa declarației funcției în primul caz și a operatorului condiționat în al doilea caz vor fi rupte. În mod similar, într-o listă de elemente separate prin virgule, nu puteți muta virgula pe linia următoare: func f ( i // ! , k int // ! , s // ! , t șir ) șir { // GREȘIT } func f ( i , k int , s , t șir ) șir { // TRUE } Când mutați o virgulă pe linia următoare, linia curentă se termină cu un identificator și un punct și virgulă este pus automat la sfârșitul acesteia, ceea ce încalcă sintaxa listei (o virgulă, așa cum am menționat mai sus, este o excepție de la regulă; compilatorul nu adaugă punct și virgulă după el). Astfel, limbajul dictează un anumit stil de scriere a codului. Compilatorul Go vine cu utilitarul gofmt, care oferă o formatare corectă și uniformă a textelor sursă. Tot textul din biblioteca standard Go este formatat de acest utilitar.

Tipuri de date încorporate

Limbajul conține un set destul de standard de tipuri de date simple încorporate: numere întregi, numere în virgulă mobilă, caractere, șiruri de caractere, booleeni și câteva tipuri speciale.

Întregi

Există 11 tipuri de numere întregi:

  • Numerele întregi cu semn de dimensiune fixă ​​- int8, int16, int32, int64. Acestea sunt numere întregi cu semn reprezentate în complement a doi , dimensiunea valorilor acestor tipuri este de 8, 16, 32, respectiv 64 de biți. Intervalul de valori este de la −2 n−1 la 2 n−1 −1, unde n este dimensiunea tipului.
  • Numerele întregi fără semn de dimensiune fixă ​​- uint8, uint16, uint32, uint64. Numărul din numele tipului, ca și în cazul precedent, specifică dimensiunea, dar intervalul de valori este de la 0 la 2 n -1.
  • intși uint sunt numere întregi semnate și, respectiv, fără semn. Dimensiunea acestor tipuri este aceeași și poate fi de 32 sau 64 de biți, dar nu este fixată de specificația limbajului și poate fi aleasă de implementare. Se presupune că pentru ei va fi aleasă cea mai eficientă dimensiune de pe platforma țintă.
  • byte - sinonim uint8. Este destinat, de regulă, să lucreze cu date binare neformatate.
  • rune este un sinonim uint32pentru , reprezintă un caracter Unicode.
  • uintptr este o valoare întreagă fără semn, a cărei dimensiune este definită de implementare, dar trebuie să fie suficient de mare pentru a stoca într-o variabilă de acest tip valoarea completă a pointerului pentru platforma țintă.

Creatorii limbajului recomandă utilizarea doar a tipului standard pentru lucrul cu numere în interiorul programului int. Tipurile cu dimensiuni fixe sunt concepute pentru a funcționa cu date primite de la sau transmise către surse externe, atunci când este important pentru corectitudinea codului să se specifice o dimensiune specifică a tipului. Tipurile sunt sinonime byteși runesunt concepute pentru a funcționa cu date binare și, respectiv, simboluri. Tipul uintptreste necesar doar pentru interacțiunea cu codul extern, de exemplu, în C.

Numere în virgulă mobilă

Numerele în virgulă mobilă sunt reprezentate de două tipuri float32și float64. Dimensiunea lor este de 32, respectiv 64 de biți, implementarea respectă standardul IEEE 754 . Gama de valori poate fi obținută din pachetul standard math.

Tipuri numerice cu precizie nelimitată

Biblioteca standard Go conține și pachetul big, care oferă trei tipuri cu precizie nelimitată: big.Int, big.Ratși big.Float, care reprezintă numere întregi, raționale și, respectiv, numere în virgulă mobilă; dimensiunea acestor numere poate fi orice și este limitată doar de cantitatea de memorie disponibilă. Deoarece operatorii din Go nu sunt supraîncărcați, operațiile de calcul pe numere cu precizie nelimitată sunt implementate ca metode obișnuite. Performanța calculelor cu numere mari, desigur, este semnificativ inferioară tipurilor numerice încorporate, dar atunci când se rezolvă anumite tipuri de probleme de calcul, utilizarea unui pachet bigpoate fi de preferată optimizării manuale a unui algoritm matematic.

Numere complexe

Limbajul oferă, de asemenea, două tipuri încorporate pentru numere complexe complex64și complex128. Fiecare valoare a acestor tipuri conține o pereche de părți reale și imaginare având tipuri, respectiv float32, float64. Puteți crea o valoare de tip complex în cod într-unul din două moduri: fie utilizând o funcție încorporată, complex()fie utilizând un literal imaginar într-o expresie. Puteți obține părțile reale și imaginare ale unui număr complex folosind funcțiile real()și imag().

var x complex128 = complex ( 1 , 2 ) // 1 + 2i y := 3 + 4i // 3 + 4i , 4 fiind un număr urmat de un sufix i // este un fmt literal imaginar . Println ( x * y ) // afișează „(-5+10i)” fmt . Println ( real ( x * y )) // afișează „-5” fmt . Println ( imagine ( x * y )) // va tipări „10” Valori booleene

Tipul boolean booleste destul de comun - include valorile predefinite trueși falsecare denotă, respectiv, adevărat și fals. Spre deosebire de C, valorile booleene din Go nu sunt numerice și nu pot fi convertite direct în numere.

Șiruri

Valorile de tip șir stringsunt matrice imuabile de octeți care conțin UTF-8. Acest lucru determină o serie de caracteristici specifice ale șirurilor de caractere (de exemplu, în cazul general, lungimea unui șir nu este egală cu lungimea matricei care îl reprezintă, adică numărul de caractere conținute în acesta nu este egal cu numărul de octeți din matricea corespunzătoare). Pentru majoritatea aplicațiilor care procesează șiruri întregi, această specificitate nu este importantă, dar în cazurile în care programul trebuie să proceseze în mod direct anumite rune (caractere Unicode), unicode/utf8este necesar un pachet care să conțină instrumente auxiliare pentru lucrul cu șiruri Unicode.

Declarații de tip

Pentru orice tip de date, inclusiv cele încorporate, pot fi declarate noi tipuri analogice care repetă toate proprietățile originalelor, dar sunt incompatibile cu acestea. Aceste noi tipuri pot, de asemenea, să declare opțional metode. Tipurile de date definite de utilizator în Go sunt pointeri (declarate cu simbolul *), matrice (declarate cu paranteze drepte), structuri ( struct), funcții ( func), interfețe ( interface), mapări ( map) și canale ( chan). Declarațiile acestor tipuri specifică tipurile și eventual identificatorii elementelor lor. Noile tipuri sunt declarate cu cuvântul cheie type:

tip PostString șir // Tastați „șir”, similar cu cel încorporat tip StringArray [] șir // Tip matrice cu elemente de tip șir type Person struct { // Struct tip nume șir // câmp al tipului șirului standard post PostString // câmp al tipului șirului personalizat declarat anterior bdate time . Time // câmp de tip Time, importat din pachetul time edate time . Timp șef * Persoană // câmpul indicator deducere []( * Persoană ) // câmp matrice } type InOutString chan string // tip de canal pentru transmiterea șirurilor tip CompareFunc func ( a , b interfață {}) int // tipul funcției.

Începând cu versiunea Go 1.9, este disponibilă și declarația de tip alias (alias-uri):

tip TitleString = șir // „TitleString” este un alias pentru tipul încorporat tip șir Integer = int64 // „Integer” este un alias pentru tipul încorporat de 64 de biți întreg

Un alias poate fi declarat fie pentru un tip de sistem, fie pentru orice tip definit de utilizator. Diferența fundamentală dintre aliasuri și declarațiile de tip obișnuit este că declarația creează un nou tip care nu este compatibil cu originalul, chiar dacă nu sunt adăugate modificări tipului original în declarație. Un alias este doar un alt nume pentru același tip, ceea ce înseamnă că aliasul și tipul original sunt complet interschimbabile.

Câmpurile de structură pot avea etichete în descriere - secvențe arbitrare de caractere cuprinse între ghilimele din spate:

// Structură cu etichete de câmp tip XMLInvoices struct { XMLName xml . Nume `xml:"INVOICES"` Versiune int `xml:"version,attr"` Factură [] * XMLInvoice `xml:"FACTURA"` }

Etichetele sunt ignorate de compilator, dar informațiile despre ele sunt plasate în cod și pot fi citite folosind funcțiile pachetului reflectinclus în biblioteca standard. În mod obișnuit, etichetele sunt folosite pentru a oferi tip de marshaling pentru stocarea și restaurarea datelor pe medii externe sau pentru a interacționa cu sisteme externe care primesc sau transmit date în propriile formate. Exemplul de mai sus folosește etichete procesate de biblioteca standard pentru a citi și scrie date în format XML.

Declarații variabile

Sintaxa pentru declararea variabilelor este rezolvată în principal în spiritul lui Pascal: declarația începe cu cuvântul cheie var, urmat de numele variabilei prin separator, apoi, prin separator, tipul acesteia.

Merge C++
var v1 int const v2 șir var v3 [ 10 ] int var v4 [] int var v5 struct { f int } var v6 * int /* aritmetica pointerului nu este acceptată */ var v7 map [ șir ] int var v8 func ( a int ) int int v1 ; const std :: stringv2 ; _ /* despre */ intv3 [ 10 ] ; int * v4 ; /* despre */ struct { int f ; } v5 ; int * v6 ; std :: unordered_map v7 ; /* despre */ int ( * v8 )( int a );

Declarația variabilei poate fi combinată cu inițializarea:

var v1 int = 100 var v2 șir = „Bună ziua!” var v3 [ 10 ] int = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } var v4 [] int = { 1000 , 2000 , 12334 } var v5 struct { f int } = { 50 } var v6 * int = & v1 var v7 map [ șir ] int = { „unu” : 1 , „două” : 2 , „trei” : 3 } var v8 func ( a int ) int = func ( a int ) int { return a + 1 }

Dacă o variabilă nu este inițializată în mod explicit atunci când o declară , atunci este inițializată automat la „valoare nulă” pentru tipul dat. Valoarea nulă pentru toate tipurile numerice este 0, pentru un tip string este șirul gol, pentru pointeri  este nil. Structurile sunt inițializate implicit cu seturi de valori zero pentru fiecare dintre câmpurile incluse în ele, elementele matricei sunt inițializate cu valori zero de tipul specificat în definiția matricei.

Reclamele pot fi grupate:

var ( i int m float )

Inferență automată de tip

Limbajul Go acceptă, de asemenea, deducerea tipului automat . Dacă o variabilă este inițializată atunci când este declarată, tipul acesteia poate fi omis - tipul expresiei care i se atribuie devine tipul variabilei. Pentru literale (numere, caractere, șiruri de caractere), standardul de limbaj definește tipuri specifice încorporate cărora le aparține fiecare astfel de valoare. Pentru a inițializa o variabilă de alt tip, trebuie aplicată literalului o conversie explicită de tip.

var p1 = 20 // p1 int - literalul întreg 20 este de tip int. var p2 = uint ( 20 ) // p2 uint - valoare turnată în mod explicit la uint. var v1 = & p1 // v1 *int este un pointer către p1, pentru care se deduce tipul int. var v2 = & p2 // v2 *uint este un pointer către p2, care este inițializat în mod explicit ca un întreg fără semn.

Pentru variabilele locale, există o formă scurtă de declarație combinată cu inițializare folosind inferența de tip:

v1 := 100 v2 := „Bună ziua!” v3 := [ 10 ] int { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } v4 := [] int { 1000 , 2000 , 12334 } v5 := struct { f int }{ 50 } v6 := & v1

Teme

Go folosește simbolul ca operator de atribuire =:

a = b // Setați variabila a la b

După cum s-a menționat mai sus, există o formă de definire a unei variabile cu inferență de tip automat combinată cu inițializare, care seamănă în exterior cu atribuirea în Pascal :

v1 := v2 // similar cu var v1 = v2

Compilatorul Go urmărește cu strictețe definițiile și atribuțiile și le distinge una de alta. Deoarece redefinirea unei variabile cu același nume este interzisă într-un singur domeniu, într-un singur bloc de cod, o variabilă poate apărea în stânga semnului :=o singură dată:

a := 10 // Declararea și inițializarea unei variabile întregi a. b := 20 // Declararea și inițializarea unei variabile întregi b. ... a := b // EROARE! Încercarea de a redefini a.

Go permite executarea mai multor sarcini în paralel:

i , j = j , i // Schimbați valorile i și j.

În acest caz, numărul de variabile din stânga semnului de atribuire trebuie să se potrivească exact cu numărul de expresii din dreapta semnului de atribuire.

Alocarea în paralel este posibilă și atunci când se utilizează :=. Particularitatea sa este că printre variabilele enumerate în stânga semnului :=, pot fi deja existente. În acest caz, vor fi create noi variabile, cele existente vor fi reutilizate. Această sintaxă este adesea folosită pentru tratarea erorilor:

x , err := SomeFunction () // Funcția returnează două valori (vezi mai jos), // două variabile sunt declarate și inițializate. if ( err != nil ) { return nil } y , err := SomeOtherFunction () // Aici este declarat doar y, err i se atribuie pur și simplu o valoare.

În ultima linie a exemplului, prima valoare returnată de funcție este atribuită noii variabile y, a doua variabilei deja existente err, care este folosită în tot codul pentru a plasa ultima eroare returnată de funcțiile apelate. Dacă nu este pentru această caracteristică a operatorului :=, în al doilea caz ar trebui să declarați o nouă variabilă (de exemplu, err2) sau să declarați separat yși apoi să utilizați alocarea paralelă obișnuită.

Go implementează semantica „copy-on-assignment”, adică o atribuire are ca rezultat realizarea unei copii a valorii variabilei originale și plasarea acelei copii într-o altă variabilă, după care valorile variabilelor sunt diferite și schimbarea uneia dintre ele nu-l schimbă pe celălalt. Cu toate acestea, acest lucru este valabil numai pentru tipurile scalare, structurile și matricele încorporate cu o lungime dată (adică tipurile ale căror valori sunt alocate pe stivă). Matricele de lungime nedefinită și mapările sunt alocate pe heap , variabilele de aceste tipuri conțin de fapt referințe la obiecte, atunci când sunt alocate, este copiată doar referința, dar nu și obiectul în sine. Uneori, acest lucru poate duce la efecte neașteptate. Luați în considerare două exemple aproape identice:

vector de tip [ 2 ] float64 // Lungimea matricei este setată explicit v1 := vector { 10 , 15.5 } // Inițializare - v1 conține matricea în sine v2 := v1 // Matricea v1 este copiată în matricea v2 v2 [ 0 ] = 25.3 // S-a schimbat doar matricea fmt v2 . Println ( v1 ) // Imprimă „[10 15.5]” - matricea originală nu s-a schimbat. fmt . Println ( v2 ) // Imprimă „[25.3 15.5]”

Aici tipul este vectordefinit ca o matrice de două numere. Atribuirea unor astfel de matrice se comportă în același mod ca și atribuirea de numere și structuri.

Și în exemplul următor, codul diferă cu exact un caracter: tipul vectoreste definit ca o matrice cu o dimensiune nedefinită. Dar acest cod se comportă complet diferit:

tip vector [] float64 // Matrice cu lungime nedefinită v1 := vector { 10 , 15.5 } // Inițializare - v1 conține referința matricei v2 := v1 // Referința matricei este copiată de la v1 la v2 v2 [ 0 ] = 25.3 / / Poate fi considerat ca schimbând numai matricea fmt v2 . Println ( v1 ) // Imprimă „[25.3 15.5]” - matricea originală s-a SCHIMBAT! fmt . Println ( v2 ) // Imprimă „[25.3 15.5]”

În același mod ca în al doilea exemplu, mapările și interfețele se comportă. Mai mult decât atât, dacă structura are un câmp de tip referință sau interfață sau un câmp este o matrice adimensională sau o mapare, atunci când se atribuie o astfel de structură, se va copia și referința, adică vor începe câmpurile diferitelor structuri. pentru a indica aceleași obiecte din memorie.

Pentru a evita acest efect, trebuie să utilizați în mod explicit o funcție de sistem copy()care să garanteze crearea unei a doua instanțe a obiectului.

Argumente la funcții și metode

sunt declarate astfel:

func f ( i , j , k int , s , t șir ) șir { }

Funcțiile pot returna mai multe valori

Tipurile de astfel de valori sunt incluse între paranteze:

func f ( a , b int ) ( int , șir ) { return a + b , „adăugare” }

Rezultatele funcției pot fi, de asemenea, denumite:

func incTwo ( a , b int ) ( c , d int ) { c = a + 1 d = b + 1 return }

Rezultatele numite sunt considerate declarate imediat după antetul funcției cu valori inițiale zero. Instrucțiunea return într-o astfel de funcție poate fi utilizată fără parametri, caz în care, după revenirea din funcție, rezultatele vor avea valorile care le-au fost atribuite în timpul execuției acesteia. Deci, în exemplul de mai sus, funcția va returna o pereche de valori întregi, cu una mai mare decât parametrii săi.

Valorile multiple returnate de funcții sunt atribuite variabilelor prin enumerarea lor separate prin virgule, în timp ce numărul de variabile cărora le este atribuit rezultatul apelului de funcție trebuie să se potrivească exact cu numărul de valori returnate de funcție:

first , second := incTwo ( 1 , 2 ) // first = 2, second = 3 first := incTwo ( 1 , 2 ) // WRONG - nicio variabilă atribuită celui de-al doilea rezultat

Pseudovariabilă „_”

Spre deosebire de Pascal și C, unde declararea unei variabile locale fără a o folosi mai târziu, sau pierderea valorii unei variabile locale (atunci când valoarea atribuită variabilei nu este citită nicăieri) poate provoca doar un avertisment al compilatorului, în Go această situație este considerată o eroare de limbaj si duce la imposibilitatea compilarii programului. Aceasta înseamnă, în special, că programatorul nu poate ignora valoarea (sau una dintre valori) returnată de funcție, pur și simplu atribuind-o unei variabile și refuzând să o folosească în continuare. Dacă devine necesară ignorarea uneia dintre valorile returnate de un apel de funcție, se folosește o pseudovariabilă predefinită numită „_” (o liniuță de subliniere). Poate fi specificat oriunde unde ar trebui să existe o variabilă care ia o valoare. Valoarea corespunzătoare nu va fi atribuită nici unei variabile și pur și simplu va fi pierdută. Sensul unei astfel de decizii arhitecturale este de a identifica în etapa de compilare o posibilă pierdere a rezultatelor calculului: o omisiune accidentală a procesării valorii va fi detectată de către compilator, iar utilizarea pseudovariabilei „_” va indica că programatorul a ignorat în mod deliberat rezultatele. În exemplul următor, dacă este necesară doar una dintre cele două valori returnate de funcția incTwo, trebuie specificat „_” în locul celei de-a doua variabile:

first := incTwo ( 1 , 2 ) // INVALID primul , _ := incTwo ( 1 , 2 ) // TRUE, al doilea rezultat nu este folosit

Variabila „_” poate fi specificată în lista de atribuiri de orice număr de ori. Toate rezultatele funcției care se potrivesc cu „_” vor fi ignorate.

Amânarea mecanismului de apelare amânată

Apelul amânat înlocuiește mai multe caracteristici sintactice simultan, în special, tratatorii de excepții și blocurile de completare garantată. Un apel de funcție precedat de cuvântul cheie defer este parametrizat în punctul din program în care este plasat și este executat imediat înainte ca programul să iasă din domeniul în care a fost declarat, indiferent de cum și din ce motiv are loc această ieșire. Dacă o singură funcție conține mai multe declarații de amânare, apelurile corespunzătoare sunt executate secvențial după ce funcția se termină, în ordine inversă. Mai jos este un exemplu de utilizare a amânării ca bloc de finalizare garantat [15] :

// Funcție care copiază fișierul func CopyFile ( dstName , srcName șir ) ( scris int64 , eroare err ) { src , err := os . Open ( srcName ) // Deschideți fișierul sursă dacă err != nil { // Verificați return // Dacă eșuează, returnați cu o eroare } // Dacă ați ajuns aici, fișierul sursă a fost deschis cu succes defer src . Close () // Apel întârziat: src.Close() va fi apelat când CopyFile se finalizează dst , err := os . Creați ( dstName ) // Deschideți fișierul destinație dacă err != nil { // Verificați și returnați la returnarea erorii } defer dst . Close () // Apel întârziat: dst.Close() va fi apelat când CopyFile se finalizează intoarcere._ _ _ Copiați ( dst , src ) // Copiați datele și reveniți din funcție // După ce toate operațiunile vor fi apelate: mai întâi dst.Close(), apoi src.Close() }

Condiții bucle și ramificații

Spre deosebire de majoritatea limbilor cu sintaxă asemănătoare C, Go nu are paranteze pentru constructele condiționate for, if, switch:

if i >= 0 && i < len ( arr ) { println ( arr [ i ]) } ... for i := 0 ; i < 10 ; i ++ { } }

Cicluri

Go folosește o construcție de bucle pentru a organiza toate tipurile de bucle  for.

for i < 10 { // buclă cu precondiție, similar cu while în C } pentru i := 0 ; i < 10 ; i ++ { // buclă cu un numărător, similar cu for din C } for { // buclă infinită // Ieșirea din buclă trebuie gestionată manual, // de obicei făcută cu return sau break } for { // buclă cu postcondiție ... // corpul buclei dacă i >= 10 { // ieșire întreruperea condiției } } for i , v := range arr { // parcurge colecția (matrice, felie, afișare) arr // i - index (sau cheie) elementului curent // v - copie a valorii elementului de matrice curent } pentru i := range arr { // parcurge colecția, este folosit doar indexul } pentru _ , v := range arr { // treceți în buclă prin colecție, utilizați numai valorile elementului } for range arr { // Buclă prin colecție fără variabile (colecția este folosită // doar ca contor de iterații). } pentru v := interval c { // buclă prin canal: // v va citi valorile de pe canalul c, // până când canalul este închis de o rutină concomitentă // }

Operator cu alegere multiplă

Sintaxa operatorului cu variante multiple switchare o serie de caracteristici. În primul rând, spre deosebire de C, nu este necesară utilizarea operatorului break: după ce ramura selectată a fost procesată, execuția operatorului se încheie. Dacă, dimpotrivă, doriți ca următoarea ramură să continue procesarea după ramura selectată, trebuie să utilizați operatorul fallthrough:

comutare valoare { cazul 1 : fmt . Println ( "One" ) fallthrough // În continuare, ramura "case 0:" va fi executată case 0 : fmt . println ( "Zero" ) }

Aici, când value==1vor fi afișate două linii, „Unul” și „Zero”.

Expresia de alegere și, în consecință, alternativele din instrucțiunea switch pot fi de orice tip, este posibil să enumerați mai multe opțiuni într-o singură ramură:

comutați caractere [ cod ]. categorie { case "Lu" , "Ll" , "Lt" , "Lm" , "Lo" : ... case "Nd" : ... implicit : ... }

Este permisă absența unei expresii de alegere, caz în care condițiile logice trebuie scrise în alternative. Se execută prima ramură, a cărei condiție este adevărată:

comutare { case '0' <= c && c <= '9' : returnează c - '0' case 'a' <= c && c <= 'f' : return c - 'a' + 10 case 'A' <= c && c <= 'F' : returnează c - 'A' + 10 }

Un detaliu important: dacă una dintre ramurile cu condiția se termină cu operatorul fallthrough, atunci după această ramură va fi procesată următoarea, indiferent dacă condiția sa este îndeplinită . Dacă doriți ca următoarea ramură să fie procesată numai dacă condiția sa este îndeplinită, trebuie să utilizați construcții secvențiale if.

Caracteristici arhitecturale

Gestionarea erorilor și excepțiilor

Limbajul Go nu acceptă sintaxa structurată de gestionare a excepțiilor tipică majorității limbilor moderne , care implică aruncarea excepțiilor cu o comandă specială (de obicei throwsau raise) și gestionarea lor într-un bloc try-catch. În schimb, este recomandat să utilizați returnarea erorii ca unul dintre rezultatele funcției (ceea ce este la îndemână, deoarece în Go o funcție poate returna mai mult de o valoare):

  • În ultimul parametru, funcția returnează un obiect de eroare sau un pointer nul nildacă funcția a fost executată fără erori. Tipul de eroare este de obicei o interfață de bibliotecă error.
  • Obiectul returnat de funcție este verificat și eroarea, dacă există, este tratată. Dacă o eroare de la site-ul apelului nu poate fi tratată în mod adecvat, este de obicei returnată ca rezultat al funcției curente sau se creează o nouă eroare pe baza acesteia și se returnează.
func ReadFile ( șir srcName ) ( șir rezultat , eroare eroare ) { fișier , eroare := os . Deschideți ( srcName ) if err != nil { // Generați o nouă eroare cu text calificativ return nil , fmt . Errorf ( "citire fișier %q: %w" , srcName , err ) } ... // Execuția ulterioară a funcției dacă nu a existat nicio eroare returnează rezultatul , nil // Returnează rezultatul și eroarea goală dacă are succes }
  • Este imposibil să ignorați eroarea returnată de funcție (în exemplul de mai sus, nu verificați valoarea variabilei err), deoarece inițializarea unei variabile fără o utilizare ulterioară în limbajul Go duce la o eroare de compilare. Această limitare poate fi ocolită prin înlocuirea pseudovariabilei err _, dar acest lucru este evident când se analizează codul.

Mulți critici ai limbii cred că această ideologie este mai rea decât gestionarea excepțiilor, deoarece numeroase verificări aglomera codul și nu permit ca toate gestionarea erorilor să fie concentrată în blocuri catch. Creatorii limbii nu consideră aceasta o problemă serioasă. Sunt descrise o serie de modele de tratare a erorilor în Go (vezi, de exemplu, articolul lui Rob Pike pe blogul oficial Go , traducere în limba rusă ), care pot reduce cantitatea de coduri de tratare a erorilor.

Când apar erori fatale care fac imposibilă executarea ulterioară a programului (de exemplu, împărțirea la zero sau accesarea limitelor matricei), apare o stare de panică , care în mod implicit duce la prăbușirea programului cu un mesaj de eroare și o urmărire a stivei de apeluri. Panicile pot fi prinse și gestionate folosind constructul de execuție amânată deferdescris mai sus. Apelul funcției specificat în deferse face înainte de a părăsi domeniul curent, inclusiv în caz de panică. În interiorul funcției numite în defer, puteți apela o funcție standard recover() - oprește procesarea sistemului de panică și returnează cauza acesteia sub forma unui obiect errorcare poate fi procesat ca o eroare normală. Dar programatorul poate relua și o panică prinsă anterior apelând standardul panic(err error).

// Programul efectuează o împărțire întreg // a primului său parametru cu al doilea // și tipărește rezultatul. func main () { defer func () { err := recover () if v , ok := err .( error ); ok { // Gestionarea unei panică corespunzătoare erorii de interfață fmt . Fprintf ( os . Stderr , "Eroare %v \"%s\"\n" , err , v . Eroare ()) } else if err != nil { panic ( err ) // Gestionarea erorilor neașteptate - ridicați din nou panică. } }() a , err := strconv . ParseInt ( os . Args [ 1 ], 10 , 64 ) if err != nil { panic ( err ) } b , err := strconv . ParseInt ( os . Args [ 2 ], 10 , 64 ) if err != nil { panic ( err ) } fmt . fprintf ( os . Stdout , "%d / %d = %d\n" , a , b , a / b ) }

În exemplul de mai sus, pot apărea erori atunci când argumentele programului sunt convertite în numere întregi de către funcția strconv.ParseInt(). De asemenea, este posibil să intrați în panică la accesarea matricei os.Args cu un număr insuficient de argumente sau la împărțirea la zero dacă al doilea parametru este zero. Pentru orice situație de eroare, se generează o panică, care este procesată în apel defer:

> împărțiți 10 5 10 / 5 = 2 > împărțiți 10 0 Eroare runtime.errorString „eroare de rulare: împărțire întreg la zero” > împărțiți 10,5 2 Eroare *strconv.NumError „strconv.ParseInt: analizarea „10.5”: sintaxă nevalidă” > împarte 10 Eroare runtime.errorString „eroare de rulare: index în afara intervalului”

O panică nu poate fi declanșată într-o rutină de execuție paralelă (vezi mai jos), ci gestionată în alta. De asemenea, nu este recomandat să „treceți” panica peste granița unui pachet.

Multithreading

Modelul de threading al lui Go a fost moștenit din limbajul Active Oberon , bazat pe CSP -ul lui Tony Hoare , folosind idei din limbajele Occam și Limbo [9] , dar sunt prezente și caracteristici precum pi-calculus și channeling.

Go vă permite să creați un nou fir de execuție a programului folosind cuvântul cheie go , care rulează o funcție anonimă sau numită într-o goroutine nou creată (termenul Go pentru corutine ). Toate goroutinele din cadrul aceluiași proces utilizează un spațiu de adresă comun, care se execută pe firele de execuție a sistemului de operare , dar fără a leagă greu la acestea din urmă, ceea ce permite unei goroutine care rulează să părăsească un fir cu o goroutine blocată (în așteptarea, de exemplu, să trimită sau să primească un mesaj de pe un canal) și continuați. Biblioteca runtime include un multiplexor pentru a partaja numărul disponibil de nuclee de sistem între goroutine. Este posibil să se limiteze numărul maxim de nuclee de procesor fizic pe care va fi executat programul. Auto-suportarea goroutinelor de către biblioteca Go runtime facilitează utilizarea unui număr mare de goroutine în programe, depășind cu mult limita numărului de fire de execuție acceptate de sistem.

func server ( i int ) { pentru { print ( i ) time . Sleep ( 10 ) } } go server ( 1 ) go server ( 2 )

Închiderile pot fi folosite într-o expresie go .

var g int go func ( i int ) { s := 0 pentru j := 0 ; j < i ; j ++ { s += j } g = s }( 1000 )

Pentru comunicarea între goroutine se folosesc canale (de tip încorporat chan ), prin care poate fi trecută orice valoare. Un canal este creat de funcția încorporată make(), căreia îi este transmis tipul și (opțional) volumul canalului. În mod implicit, volumul canalului este zero. Astfel de canale sunt nebufferizate . Puteți seta orice volum întreg pozitiv al canalului, apoi va fi creat un canal tamponat .

O țeavă fără tampon sincronizează strâns firele de citire și scriere care o folosesc. Când un thread de scriitor scrie ceva într-o conductă, se întrerupe și așteaptă până când valoarea a fost citită. Când un thread de citire încearcă să citească ceva dintr-o conductă în care a fost deja scris, citește valoarea și ambele fire pot continua să se execute. Dacă nicio valoare nu a fost încă scrisă pe canal, firul de citire se întrerupe și așteaptă ca cineva să scrie pe canal. Adică, conductele fără tampon din Go se comportă la fel ca și conductele din Occam sau mecanismul de întâlnire în limbajul Ada .

Un canal tamponat are un buffer de valoare a cărui dimensiune este egală cu dimensiunea canalului. Când scrieți într-o astfel de conductă, valoarea este plasată în buffer-ul conductei, iar firul de scriere continuă fără pauză, cu excepția cazului în care bufferul conductei este plin în momentul scrierii. Dacă tamponul este plin, atunci firul de scriere este suspendat până când cel puțin o valoare a fost citită de pe canal. Firul de citire citește și o valoare dintr-o țeavă tamponată fără pauză dacă există valori necitite în țeavă tampon; dacă buffer-ul canalului este gol, atunci firul se întrerupe și așteaptă până când un alt thread îi scrie o valoare.

Când ați terminat, canalul poate fi închis cu funcția încorporată close(). O încercare de a scrie pe un canal privat duce la o panică, citirea de pe un canal privat are loc întotdeauna fără întrerupere și citește valoarea implicită. Dacă canalul este în buffer și la momentul închiderii conține N valori scrise anterior în buffer, atunci primele N operații de citire vor fi efectuate ca și cum canalul ar fi încă deschis și vor citi valorile din buffer și numai după aceea citirea de pe canal va returna valorile implicite.

Operația este folosită pentru a transmite o valoare către și de la un canal <-. Când scrieți pe un canal, este folosit ca operator binar, când citiți - ca operator unar:

in := make ( chan string , 0 ) // Creați un canal nebuffered in out := make ( chan int , 10 ) // Creați un canal tamponat out ... in <- arg // Scrieți o valoare canalului în ... r1 := <- out // citire din canal out ... r2 , ok := <- out // citire cu verificarea daca canalul este inchis if ok { // if ok == true - canalul este deschis ... } else { // dacă canalul este închis, faceți altceva ... }

Operația de citire dintr-un canal are două opțiuni: fără verificare și cu verificare pentru închiderea canalului. Prima opțiune (citirea r1 în exemplul de mai sus) citește pur și simplu următoarea valoare în variabilă; dacă canalul este închis, valoarea implicită va fi citită în r1. A doua opțiune (citirea r2) citește, în plus față de valoare, o valoare booleană - indicatorul de stare al canalului ok, care va fi adevărat dacă datele plasate acolo de orice flux au fost citite de pe canal și false dacă canalul este închis și tamponul său este gol. Cu această operație, firul de citire poate determina când canalul de intrare este închis.

Citirea dintr-o țeavă este, de asemenea, acceptată folosind construcția buclei for-range:

// Funcția începe citirea paralelă de pe canalul de intrare în numere întregi și scrierea // în canalul de ieșire numai a acelor întregi care sunt pozitive. // Returnează canalul de ieșire. func pozitive ( in < -chan int64 ) < -chan int64 { out := make ( chan int64 ) go func () { // Bucla va continua până când canalul in este închis pentru următorul := interval în { if next > 0 { ieși <- următorul } } închide ( ieși ) } () întoarce afară }

În plus față de CSP sau împreună cu mecanismul de canalizare, Go vă permite, de asemenea, să utilizați modelul obișnuit de interacțiune sincronizată a firelor de execuție prin intermediul memoriei partajate, folosind instrumente tipice de sincronizare a accesului, cum ar fi mutexurile . În același timp, însă, specificația limbajului avertizează împotriva oricăror încercări de interacțiune nesincronizată a firelor paralele prin intermediul memoriei partajate, deoarece în absența sincronizării explicite, compilatorul optimizează codul de acces la date fără a ține cont de posibilitatea accesului simultan de la diferite fire, care pot duce la erori neașteptate. De exemplu, scrierea valorilor în variabilele globale într-un fir poate să nu fie vizibilă sau vizibilă în ordine greșită dintr-un fir paralel.

De exemplu, luați în considerare programul de mai jos. Codul funcției este main()scris pe presupunerea că funcția lansată în goroutine setup()va crea o structură de tip T, o va inițializa cu șirul „hello, world” și apoi va atribui o referință la structura inițializată variabilei globale g. B main()pornește o buclă goală, așteptând gsă apară o valoare diferită de zero. Imediat ce apare, main()tipărește un șir din structura indicată g, presupunând că structura a fost deja inițializată.

tip T struct { șir mesaj } varg * T _ func setup () { t : = new ( T ) t . msg = "bună ziua, lume" g = t } func main () { go setup () for g == nil { // NU FUNCTIONEAZA!!! } print ( g . msg ) }

În realitate, una dintre cele două erori este posibilă.

  • Este posibil ca firul principal să nu vadă schimbarea variabilei gși apoi programul se va bloca într-o buclă nesfârșită. Acest lucru se poate întâmpla dacă un compilator configurat pentru optimizare agresivă determină că setup()valoarea creată nu este transmisă nicăieri și pur și simplu elimină întregul cod al acestei funcții ca fiind nesemnificativ.
  • Firul principal va vedea că valoarea gnu mai este nulă, dar valoarea nu va fi inițializată în g.msgmomentul în care funcția este executată; print()în acest caz, programul va scoate un șir gol. Acest lucru se poate întâmpla dacă compilatorul, în scopuri de optimizare, elimină variabila locală tși scrie o referință la obiectul creat direct în g.

Singura modalitate corectă de organizare a transferului de date prin memoria partajată este utilizarea instrumentelor de sincronizare a bibliotecii, care garantează că toate datele scrise de unul dintre fluxurile sincronizate înainte de punctul de sincronizare sunt garantate a fi disponibile într-un alt flux sincronizat după punctul de sincronizare.

O caracteristică a multithreading-ului în Go este că o goroutine nu este identificată în niciun fel și nu este un obiect limbaj la care se poate face referire atunci când se apelează funcții sau care poate fi plasat într-un container. În consecință, nu există mijloace care să vă permită să influențați direct execuția unei corutine din exteriorul acesteia, cum ar fi suspendarea și apoi pornirea acesteia, schimbarea priorității, așteptarea finalizării unei corutine în alta și întreruperea forțată a execuției. Orice acțiune asupra unui goroutine (altul decât terminarea programului principal, care termină automat toate goroutinele) poate fi făcută numai prin conducte sau alte mecanisme de sincronizare. Următorul este exemplu de cod care pornește mai multe goroutine și așteaptă ca acestea să se finalizeze folosind obiectul de sincronizare WaitGroup din pachetul de sistem de sincronizare. Acest obiect conține un contor, inițial zero, care poate crește și decrementa, și o metodă Wait(), care face ca firul de execuție curent să se întrerupă și să aștepte până când contorul este resetat la zero.

func main () { var wg sync . WaitGroup // Creați un grup de așteptare. Valoarea inițială a contorului este 0 logger := log . Nou ( os . Stdout , "" , 0 ) // log.Logger este un tip de ieșire thread-safe pentru _ , arg := range os . Args { // Buclă prin toate argumentele liniei de comandă wg . Adăugați ( 1 ) // Creșteți contorul grupului de așteptare cu unul // Rulați o rutină pentru a procesa parametrul arg go func ( șir de cuvinte ) { // Scăderea întârziată a contorului grupului de așteptare cu una. // Se întâmplă când funcția se termină. amână wg . Terminat () logger . Println ( prepareWord ( cuvânt )) // Efectuați procesarea și imprimați rezultatul }( arg ) } wg . Wait () // Așteptați până când contorul din waitgroup wg este zero. }

Aici, înainte de crearea fiecărei noi gorutine, contorul obiectului wg este incrementat cu unu, iar după finalizarea gorutinei, acesta este decrementat cu unu. Ca urmare, în bucla care începe procesarea argumentelor, la contor vor fi adăugate atâtea unități câte goroutine sunt lansate. Când bucla se termină, apelarea wg.Wait() va face ca programul principal să se întrerupă. Pe măsură ce fiecare dintre goroutine se termină, decrește contorul wg cu una, astfel încât așteptarea programului principal se va încheia când se vor termina atâtea goroutine câte rula. Fără ultima linie, programul principal, după ce a rulat toate goroutinele, ar ieși imediat, întrerupând execuția celor care nu au avut timp să se execute.

În ciuda prezenței multithreadingului încorporat în limbaj, nu toate obiectele limbajului standard sunt sigure pentru fire. Deci, tipul de hartă standard (afișajul) nu este sigur pentru fire. Creatorii limbajului au explicat această decizie cu considerente de eficiență, deoarece asigurarea securității pentru toate astfel de obiecte ar duce la supraîncărcare suplimentară, ceea ce este departe de a fi întotdeauna necesar (aceleași operațiuni cu mapări pot face parte din operațiuni mai mari care sunt deja sincronizate de programator). , iar apoi sincronizarea suplimentară nu va face decât să complice și să încetinească programul). Începând cu versiunea 1.9, pachetul de bibliotecă de sincronizare, care conține suport de procesare paralelă, a adăugat tipul de sincronizare sigură pentru fire.Map, care poate fi folosit dacă este necesar. De asemenea, puteți acorda atenție tipului thread-safe utilizat în exemplu pentru a afișa rezultatele log.Logger; este folosit în locul pachetului fmt standard, ale cărui funcții (Printf, Println și așa mai departe) nu sunt sigure pentru fire și ar necesita sincronizare suplimentară.

Programare orientată pe obiecte

Nu există un cuvânt cheie special pentru declararea unei clase în Go, dar metodele pot fi definite pentru orice tip numit, inclusiv structuri și tipuri de bază precum int , deci în sensul OOP toate astfel de tipuri sunt clase.

tastați newInt int

Sintaxa definiției metodei este împrumutată din limbajul Oberon-2 și diferă de definiția obișnuită a funcției prin aceea că după cuvântul cheie func, așa-numitul „receptor” ( în engleză  receiver ) este declarat în paranteze , adică obiectul pentru care este numită metoda și tipul căruia îi aparține metoda. În timp ce în limbajele obiect tradiționale receptorul este implicit și are un nume standard (în C++ sau Java este „acest lucru”, în ObjectPascal este „self”, etc.), în Go este specificat în mod explicit și numele său poate fi orice identificator Go valid.

type myType struct { i int } // Aici p este receptorul în metodele de tip myType. func ( p * myType ) get () int { return p . i } func ( p * myType ) set ( i int ) { p . i = i }

Nu există o moștenire formală a claselor (structurilor) în Go, dar există un mecanism de încorporare apropiat din punct de vedere tehnic .  În descrierea structurii, puteți utiliza așa-numitul câmp anonim - un câmp pentru care nu este indicat un nume, ci doar un tip. Ca rezultat al unei astfel de descrieri, toate elementele structurii încorporate vor deveni elementele cu același nume ale structurii încorporate.

// Nou tip de struct tip myType2 struct { myType // Câmpul anonim oferă încorporarea tipului myType. // Acum myType2 conține câmpul i și metodele get() și set(int). k int }

Spre deosebire de moștenirea clasică, integrarea nu implică un comportament polimorf (un obiect al unei clase de încorporare nu poate acționa ca obiect al unei clase încorporabile fără conversie explicită de tip).

Nu este posibil să declarați în mod explicit metode pentru un tip fără nume (sintaxa pur și simplu nu vă permite să specificați tipul receptorului în metodă), dar această limitare poate fi ocolită cu ușurință prin introducerea tipului numit cu metodele necesare.

Polimorfismul clasei este furnizat în Go prin mecanismul interfețelor (similar cu clasele complet abstracte în C++ ). O interfață este descrisă folosind cuvântul cheie interfață; în interiorul (spre deosebire de declarațiile de tip clasă) descrierile declară metodele furnizate de interfață.

tastați interfața myInterface { get () int set ( i int ) }

În Go, nu este necesar să se precizeze în mod explicit că un tip implementează o anumită interfață. În schimb, regula este că fiecare tip care oferă metode definite într-o interfață poate fi folosit ca implementare a acelei interfețe. Tipul declarat mai sus myTypeimplementează interfața myInterface, deși acest lucru nu este menționat în mod explicit nicăieri, deoarece conține metode get()și set(), ale căror semnături se potrivesc cu cele descrise în myInterface.

La fel ca și clasele, interfețele pot fi aliniate:

tastați mySecondInterface interfață { myInterface // la fel ca și declarați explicit get() int; set(i int) schimbare ( i int ) int }

Aici, interfața mySecondInterface moștenește interfața myInterface (adică declară că expune metodele incluse în myInterface) și în plus declară o metodă nativă change().

Deși este posibil, în principiu, să construiți un program Go într-o ierarhie de interfețe, așa cum se face în alte limbaje obiect, și chiar să simulați moștenirea, acest lucru este considerat o practică proastă. Limbajul dictează nu o abordare ierarhică, ci o compozițională a sistemului de clase și interfețe. Clasele de structură cu această abordare pot rămâne în general independente din punct de vedere formal, iar interfețele nu sunt combinate într-o singură ierarhie, ci sunt create pentru aplicații specifice, dacă este necesar, înglobând pe cele existente. Implementarea implicită a interfețelor în Go oferă acestor mecanisme o flexibilitate extremă și un minim de dificultăți tehnice în utilizarea lor.

Această abordare a moștenirii este în conformitate cu unele tendințe practice în programarea modernă. Deci, în celebra carte „gașca celor patru” ( Erich Gamma și alții) despre modelele de design , în special, este scris:

Dependența de implementare poate cauza probleme atunci când se încearcă reutilizarea unei subclase. Dacă chiar și un aspect al implementării moștenite este nepotrivit pentru noul domeniu, atunci clasa părinte trebuie rescrisă sau înlocuită cu ceva mai adecvat. Această dependență limitează flexibilitatea și reutilizarea. Problema poate fi depășită doar prin moștenirea din clase abstracte, deoarece acestea de obicei nu au implementare sau o implementare minimă.

Nu există un concept de funcție virtuală în Go . Polimorfismul este asigurat de interfețe. Dacă o variabilă de tip obișnuit este folosită pentru a apela o metodă, atunci un astfel de apel este legat static, adică metoda definită pentru acest tip particular este întotdeauna apelată. Dacă metoda este apelată pentru o variabilă de tip „interfață”, atunci un astfel de apel este legat dinamic, iar în momentul execuției, varianta de metodă care este definită pentru tipul obiectului efectiv alocat în momentul apelării acestuia. variabila este selectată pentru lansare.

Suportul dinamic pentru programarea orientată pe obiecte pentru Go este oferit de proiectul GOOP .

Reflecție

Capacitatea de a introspecta în timpul execuției, adică accesul și procesarea valorilor de orice tip și ajustarea dinamică a tipurilor de date procesate, este implementată în Go folosind pachetul de sistem reflect. Acest pachet vă permite să:

  • determinați tipul oricărei valori;
  • comparați oricare două valori pentru echivalență, inclusiv cele care nu sunt comparate de instrumente de limbaj standard, de exemplu, felii;
  • lucrați cu același cod cu valori de orice tip (un tip reflect.Valuevă permite să reprezentați o valoare a oricărui tip de limbă și să o convertiți într-unul dintre tipurile standard, dacă o astfel de conversie este posibilă);
  • modificați orice valoare, dacă o astfel de modificare este posibilă în principiu (de exemplu, modificați o parte a unui șir);
  • explorați tipurile, în special, câmpurile structurii de acces și descriptorii acestora, obțineți liste de metode de tip, descrierile acestora;
  • apelați funcții și metode arbitrare.

Pachetul reflectconține și multe instrumente auxiliare pentru efectuarea operațiunilor în funcție de starea dinamică a programului.

Programare la nivel scăzut

Facilități de acces la memorie de nivel scăzut sunt concentrate în pachetul de sistem unsafe. Particularitatea sa este că, deși arată ca un pachet Go obișnuit, este implementat de compilator însuși. Pachetul unsafeoferă acces la reprezentarea internă a datelor și la pointerii de memorie „reali”. Oferă caracteristici:

  • unsafe.Sizeof() - argumentul poate fi o expresie de orice tip, funcția returnează dimensiunea reală a operandului în octeți, inclusiv memoria neutilizată care poate apărea în structuri din cauza alinierii;
  • unsafe.Alignof() - argumentul poate fi o expresie de orice tip, funcția returnează dimensiunea în octeți, conform căreia tipurile operandului sunt aliniate în memorie;
  • unsafe.Offsetof() - argumentul trebuie să fie un câmp al structurii, funcția returnează offset-ul în octeți la care se află acest câmp în structură.

Pachetul oferă, de asemenea, un tip unsafe.Pointercare poate fi convertit la orice pointer și poate fi convertit într-un pointer de orice tip, precum și un tip standard uintptr , o valoare întreagă fără semn suficient de mare pentru a stoca o adresă completă pe platforma curentă. Prin conversia indicatorului în unsafe.Pointerși apoi în uintptr, puteți obține adresa ca un întreg, căruia îi puteți aplica operații aritmetice. Prin convertirea valorii înapoi la unsafe.Pointerși într-un pointer către orice tip anume, puteți accesa aproape oriunde în spațiul de adrese în acest fel.

Transformările descrise pot fi nesigure, așa că se recomandă să fie evitate dacă este posibil. În primul rând, există probleme evidente asociate cu accesul eronat la zona de memorie greșită. Un aspect mai subtil este că, în ciuda utilizării pachetului unsafe, obiectele Go continuă să fie gestionate de managerul de memorie și de colectorul de gunoi. Convertirea unui pointer într-un număr scapă de sub control acel pointer, iar programatorul nu se poate aștepta ca un astfel de pointer convertit să rămână relevant pe termen nelimitat. De exemplu, încercarea de a stoca un pointer către un obiect de tip nou, Тastfel:

pT := uintptr ( unsafe . Pointer ( new ( T ))) // GREȘIT!

va face ca obiectul să fie creat, indicatorul către acesta convertit într-un număr (care va fi atribuit pT). Cu toate acestea, pTare un tip întreg și nu este considerat de către colectorul de gunoi ca fiind un pointer către un obiect creat, așa că după finalizarea operațiunii, sistemul de management al memoriei va considera acest obiect neutilizat. Adică, poate fi eliminat de către colectorul de gunoi, după care pointerul convertit pTva deveni invalid. Acest lucru se poate întâmpla în orice moment, atât imediat după finalizarea operațiunii, cât și după multe ore de funcționare a programului, astfel încât eroarea se va exprima în blocări aleatorii ale programului, a căror cauză va fi extrem de greu de identificat. Și atunci când utilizați un colector de gunoi în mișcare [* 1] , indicatorul convertit într-un număr poate deveni irelevant chiar și atunci când obiectul nu a fost încă eliminat din memorie.

Deoarece specificația Go nu oferă indicații precise cu privire la măsura în care un programator se poate aștepta să țină actualizat un pointer convertit într-un număr, există o recomandare: să mențineți astfel de conversii la minimum și să le organizați astfel încât conversia de pointerul original, modificările sale și conversia înapoi sunt într-o singură instrucțiune de limbă, iar atunci când apelați orice funcții de bibliotecă care returnează o adresă sub forma uintptr, convertiți imediat rezultatul lor în pentru unsafe.Pointera păstra garanția că pointerul nu va fi pierdut.

Pachetul unsafeeste rareori folosit direct în programarea aplicațiilor, dar este folosit în mod activ în pachetele reflect, os, syscall, contextși netaltele.

Interfață cu cod în alte limbi

Există mai multe instrumente externe care oferă interfețe cu funcție străină (FFI) pentru programele Go. Utilitarul cgo poate fi folosit pentru a interacționa cu codul C extern (sau având o interfață compatibilă cu C) . Este apelat automat atunci când compilatorul procesează un modul Go scris corect și asigură crearea unui pachet temporar Go wrapper care conține declarații de toate tipurile și funcțiile necesare. În apelurile de funcții C, de multe ori trebuie să recurgeți la facilități de pachet , folosind în principal . Un instrument mai puternic este SWIG [16] , care oferă caracteristici mai avansate, cum ar fi integrarea cu clasele C++ . unsafeunsafe.Pointer

Interfata utilizator

Biblioteca standard Go acceptă crearea de aplicații de consolă și aplicații de server bazate pe web , dar nu există instrumente standard pentru crearea interfețelor grafice în aplicațiile client. Acest decalaj este umplut de wrapper-uri terță parte pentru cadrele UI populare, cum ar fi GTK+ și Qt , sub Windows puteți utiliza instrumentele grafice WinAPI accesându-le prin pachet syscall, dar toate aceste metode sunt destul de greoaie. Există, de asemenea, mai multe dezvoltări ale cadrelor UI în Go însuși, dar niciunul dintre aceste proiecte nu a atins nivelul de aplicabilitate industrială. În 2015, la conferința GopherCon de la Denver , unul dintre creatorii limbajului, Robert Grismer, a răspuns la întrebări, a fost de acord că Go are nevoie de un pachet UI, dar a remarcat că un astfel de pachet ar trebui să fie universal, puternic și multi-platformă, ceea ce îi face dezvoltare lungă şi dificilă.proces. Problema implementării unui client GUI în Go este încă deschisă.

Critica

Datorită tinereții limbii, criticile sale se concentrează în principal în articole, recenzii și forumuri de pe Internet.

Lipsa oportunităților

O mare parte din criticile la adresa limbii se concentrează pe lipsa anumitor caracteristici populare oferite de alte limbi. Printre ei [17] [18] [19] [20] :

După cum sa menționat mai sus, lipsa unui număr de funcții disponibile în alte limbi populare se datorează alegerii conștiente a dezvoltatorilor, care cred că astfel de caracteristici fie împiedică compilarea eficientă, fie provoacă programatorul să facă greșeli sau creează ineficiente sau „rău” în ceea ce privește întreținerea codului sau au alte reacții adverse nedorite.

Arhitectură

  • Fără tipuri enumerate. În schimb sunt folosite grupuri de constante, dar toate constantele valorice sunt de fapt numere întregi, aceste grupuri nu sunt combinate sintactic, iar compilatorul nu are control asupra utilizării lor. Nu este posibil să descrii un tip asociat cu o enumerare, de exemplu, o matrice care conține câte un element pentru fiecare element al unei enumerații (care în Pascal este descris de constructul kind type EnumArray = array[EnumType] of ElementType), creați o buclă peste enumerare, compilatorul nu poate controla completitudinea listei de alternative din construct switch, atunci când valoarea este folosită ca enumerari selector.
  • Insuficiența tipurilor de date container încorporate.
    Containerele încorporate în limbaj sunt limitate la matrice și mapări. Containerele implementate prin intermediul limbajului în sine (inclusiv cele incluse în biblioteca standard) nu sunt sigure de tipul din cauza utilizării forțate a elementelor de tipul din ele interface{}și, în plus, nu pot fi ocolite folosind for range.
  • Absența unei indicații explicite a implementării interfeței după tip face dificilă înțelegerea codului, modificarea și refactorizarea acestuia . Compilatorul nu poate verifica automat tipul față de interfețele implementate. De asemenea, este posibilă (deși puțin probabil) să „implementați accidental” o interfață, unde metodele unui tip au aceleași semnături ca și metodele interfeței, dar nu sunt în esență o implementare a comportamentului reprezentat de interfață.
  • Renunțarea la gestionarea excepțiilor structurale în favoarea returnării erorilor face imposibilă concentrarea gestionării erorilor într-un singur loc, verificările de eroare aglomera codul și îl fac dificil de înțeles. În plus, mecanismul de gestionare a stării de panică nu este în esență diferit de gestionatorii de excepții din try-catch. Mai mult decât atât, contrar propriilor recomandări, autorii limbajului folosesc generarea și procesarea de panică pentru a gestiona erorile logice din biblioteca standard.
  • Etichetele de câmp de structură nu sunt controlate de compilator.
    Etichetele care stabilesc proprietăți suplimentare ale câmpurilor de structură sunt doar șiruri care sunt procesate dinamic, nu există nici măcar cele mai simple restricții sintactice asupra formatului lor. Acest lucru se face astfel încât să nu restricționeze dezvoltatorul în utilizarea etichetelor, dar în practică duce la faptul că nicio eroare la scrierea unei etichete nu poate fi detectată în etapa de compilare.

„Capcane” (implementarea nereușită a unor mijloace)

Criticii subliniază că unele caracteristici ale Go sunt implementate în ceea ce privește cea mai simplă sau mai eficientă implementare, dar nu îndeplinesc „ principiul celei mai mici surprize ”: comportamentul lor diferă de ceea ce s-ar aștepta programatorul pe baza intuiției și experienței anterioare. Astfel de caracteristici necesită o atenție sporită a programatorului, fac dificilă învățarea și trecerea de la alte limbi.

  • Într-o buclă de colecție, variabila valoare este o copie, nu o referință.
    Într-o buclă ca " for index, value := range collection", variabila valueeste o copie a elementului curent. Operația de atribuire a unei noi valori acestei variabile este disponibilă, dar, contrar așteptărilor, nu modifică elementul curent al colecției.
  • Interfața nulă nu este egală cu interfața obiectului nul.
    O valoare de tip „interfață” este o structură de două referințe - la tabelul de metode și la obiectul însuși. La interfața nulă, ambele câmpuri sunt egale nil. O interfață care indică un obiect nul are prima referință umplută; nu este egală cu interfața nulă, deși de obicei nu există nicio diferență între cele două în ceea ce privește logica programului [21] . Acest lucru duce la efecte neașteptate și îngreunează verificarea corectitudinii valorilor tipurilor de interfețe:
interfață de tip I { f () } tip T struct {} func ( T ) f () { ... } // Tipul T implementează interfața I. main () { var t * T = nil // t este un pointer nul la tipul T. var i I = t // Scrieți un pointer nul către T într-o variabilă de interfață. dacă eu != nil { // ! Surprinde. Deși mi s-a atribuit un pointer nul, i != nil i . f () // Acest apel va avea loc și va provoca o panică. } ... } Deși iindicatorul nul către obiect a fost scris în variabilă, valoarea în sine inu este goală și comparația i != nildă un rezultat pozitiv. Pentru a vă asigura că o variabilă de interfață indică un obiect valid, trebuie să utilizați reflectarea, ceea ce complică semnificativ codul: dacă eu != nul && ! reflecta . ValueOf ( i ). isnil () { ...
  • Semantică de atribuire neomogenă chiar și pe tipuri strâns legate.
    Tipurile și structurile încorporate sunt atribuite după valoare, interfețele sunt atribuite prin referință. Matricele cu lungimea declarată static sunt atribuite după valoare, tablourile fără lungimea și afișarea declarate sunt atribuite prin referință. De fapt, alegerea semanticii de atribuire pentru un tip este determinată de modul în care valorile acestui tip sunt alocate în memorie, adică limbajul este definit de implementare.
  • Comportament diferit al operațiilor pe matrice și felii în condiții diferite.
    De exemplu, o funcție standard append()care adaugă elemente la o matrice poate crea și returna o nouă matrice sau poate adăuga și returna una existentă, în funcție de faptul dacă există suficient spațiu liber în ea pentru a adăuga elemente. În primul caz, modificările ulterioare ale matricei rezultate nu vor afecta originalul, în al doilea caz, acestea vor fi reflectate în acesta. Acest comportament forțează utilizarea constantă a funcției de copiere copy().

Alte caracteristici

Deseori criticat este mecanismul punctului și virgulă automat, din cauza căruia unele forme de scriere a declarațiilor, apelurilor de funcții și listelor devin incorecte. Comentând această decizie, autorii limbajului notează [9] că, împreună cu prezența unui formatator de cod în trusa oficială de instrumente, gofmta condus la fixarea unui standard destul de rigid de codare în Go. Cu greu este posibil să se creeze un standard de scriere a codului care să se potrivească tuturor; introducerea în limbaj a unei caracteristici care în sine stabilește un astfel de standard, unifică aspectul programelor și elimină conflictele neprincipiale din cauza formatării, care este un factor pozitiv pentru dezvoltarea și întreținerea de grup a software-ului.

Distribuție și perspective

Popularitatea lui Go a crescut în ultimii ani: din 2014 până în 2020, Go a urcat de pe locul 65 pe locul 11 ​​în clasamentul TIOBE , valoarea ratingului pentru august 2020 fiind de 1,43%. Conform rezultatelor unui sondaj dou.ua [22] , limba Go în 2018 a devenit al nouălea în lista celor mai utilizate și al șaselea în lista limbilor cărora dezvoltatorii le acordă preferințe personale.

De la prima lansare publică în 2012, utilizarea limbii a crescut constant. Lista companiilor care folosesc limbajul în dezvoltarea industrială publicată pe site-ul proiectului Go include câteva zeci de nume. S-a acumulat o gamă largă de biblioteci pentru diverse scopuri. Lansarea versiunii 2.0 a fost planificată pentru 2019, dar lucrările au fost amânate și sunt încă în curs pentru a doua jumătate a anului 2022. Se așteaptă să apară o serie de caracteristici noi , inclusiv generice și sintaxă specială pentru a simplifica gestionarea erorilor, a căror absență este una dintre cele mai frecvente plângeri ale criticilor limbajului .

Serverul web RoadRunner (server de aplicații) a fost dezvoltat în Golang , ceea ce permite aplicațiilor web să atingă o viteză de solicitare-răspuns de 10-20 ms în loc de tradiționala 200 ms. Acest serviciu web este planificat să fie inclus în cadre populare, cum ar fi Yii .

Alături de C++ , Golang este folosit pentru a dezvolta microservicii, ceea ce vă permite să „încărcați” platformele cu mai multe procesoare cu lucru. Puteți interacționa cu un microserviciu folosind REST , iar limbajul PHP este excelent pentru asta.

Spiral Framework a fost dezvoltat cu ajutorul PHP și Golang. [23]

Versiuni

Principii de numerotare și compatibilitate cu versiunile

Există o singură versiune majoră a limbajului Go în sine, versiunea 1. Versiunile mediului de dezvoltare Go (compilator, instrumente și biblioteci standard) sunt numerotate fie din două cifre ("<versiune de limbă>.<versiune majoră>"), fie trei cifre ("<versiune lingvistică>.< lansare majoră>.<versiune minoră>") către sistem. Lansarea unei noi versiuni „din două cifre” încetează automat suportul pentru versiunea anterioară „din două cifre”. Versiunile „cu trei cifre” sunt lansate pentru a remedia erorile raportate și problemele de securitate; corecțiile de securitate din astfel de versiuni pot afecta ultimele două versiuni „cu două cifre” [24] .

Autorii au declarat [25] dorința de a păstra, pe cât posibil, retrocompatibilitatea în cadrul versiunii principale a limbajului. Aceasta înseamnă că înainte de lansarea Go 2, aproape orice program creat în mediul Go 1 se va compila corect în orice versiune ulterioară a Go 1.x și va rula fără erori. Excepții sunt posibile, dar sunt puține. Cu toate acestea, compatibilitatea binară între versiuni nu este garantată, așa că un program trebuie să fie complet recompilat atunci când se trece la o versiune ulterioară a Go.

Du-te 1

Din martie 2012, când a fost introdus Go 1, au fost lansate următoarele versiuni majore:

  • go 1 - 28 martie 2012 - Prima lansare oficială; biblioteci remediate, modificări de sintaxă.
  • du-te 1.1 - 13 mai 2013 - împărțirea întregului la zero a devenit o eroare de sintaxă, au fost introduse valorile metodei - închiderile de metodă cu o valoare sursă dată, în unele cazuri utilizarea returnării a devenit opțională; implementarea are voie să aleagă între reprezentarea pe 32 de biți și 64 de biți a tipului întreg standard, modificări în suportul Unicode.
  • go 1.2 - 1 decembrie 2013 - orice încercare de a accesa indicatorul zero este garantat să provoace o panică, sunt introduse felii cu trei indici. Îmbunătățiri la Unicode.
  • go 1.3 - 18 iunie 2014 - schimbat modelul de alocare a memoriei; a eliminat suportul pentru platforma Windows 2000, a adăugat DragonFly BSD, FreeBSD, NetBSD, OpenBSD, Plan 9, Solaris.
  • du-te 1.4 - 10 decembrie 2014 - este permisă construcția buclei " pentru intervalul x { ... } " (o buclă printr-o colecție fără a utiliza variabile), dereferențiarea dublă automată este interzisă la apelarea unei metode (dacă x ** T este un indicator dublu la tipul T, apoi apelând metoda pentru x sub forma xm() - interzis); La implementare a fost adăugat suport pentru Android, NaCl pe ARM, Plan9 pe platformele AMD64.
  • go 1.5 - 19 august 2015 - în notarea literalelor hărții, indicarea tipului fiecărui element este făcută opțională, în implementare runtime-ul și compilatorul sunt complet rescrise în Go și assembler, limbajul C nu mai este folosit.
  • go 1.6 - 17 februarie 2016 - fără modificări de limbă, mediu portat pe Linux pe MIPS pe 64 de biți, Android pe x86 pe 32 de biți (android/386), setul de instrumente se modifică.
  • go 1.7 - 16 august 2016 - S-a redus timpul de compilare și dimensiunea binarelor, a crescut viteza și a adăugat pachetul de context la biblioteca standard.
  • go 1.8 - 7 aprilie 2017 - colectorul de gunoi de memorie încorporat a fost accelerat, modulul „http” a primit capacitatea de oprire soft, a fost adăugat suport pentru procesoare cu arhitectura MIPS (32 de biți). Au fost făcute corecții la o serie de pachete și utilități.
  • go 1.9 - 24 august 2017 - au fost adăugate în limbaj aliasuri de nume de tip, au fost clarificate unele puncte de utilizare a operațiunilor în virgulă mobilă, au fost optimizate instrumente, au fost adăugate biblioteci, în special, tipul de hartă thread-safe.
  • go 1.10 - 16 februarie 2018 - au fost făcute două precizări la limbaj, care de fapt au legitimat implementările existente, restul modificărilor se referă la biblioteci și instrumente. Au fost lansate trei versiuni „din trei cifre” 1.10.1 - 1.10.3, care conțin remedieri pentru erorile detectate.
  • go 1.11 - 24 august 2018 - a adăugat suport (ca experimental) pentru module (noul mecanism de versiune a pachetelor și gestionarea dependențelor), precum și capacitatea de a compila în WebAssembly , suport îmbunătățit pentru procesoarele ARM, au fost aduse modificări setului de instrumente și bibliotecilor (în special, a adăugat pachetul syscall/js; compilatorul controlează acum corect utilizarea variabilelor declarate în instrucțiunile switch cu verificarea tipului).
  • go 1.12 - 25 februarie 2019 - remedieri în biblioteci și utilități. Anunțat ca ultima versiune care păstrează suportul pentru FreeBSD 10.X și macOS 10.10. S-a adăugat suport CGO pe platforma linux/ppc64. S-a adăugat suport pentru sistemul de operare AIX . Până în august 2019, nouă versiuni de patch-uri au fost lansate ca parte a acestei versiuni, reparând diverse erori.
  • go 1.13 - 3 septembrie 2019 - limbii au fost adăugate noi literale numerice: numere întregi binare și octale, virgulă mobilă hexazecimală (acesta din urmă trebuie să conțină exponentul, despărțit prin simbolul p sau P); a permis utilizarea caracterelor de subliniere pentru a separa cifrele în numere; operația de deplasare pe biți este permisă pentru numere întregi cu semn; a adăugat suport pentru Android 10; suportul pentru versiunile mai vechi a fost întrerupt pe o serie de platforme.
  • go 1.14 - 25 februarie 2020 - Definiția includerii interfețelor a fost extinsă: acum este permisă includerea mai multor interfețe care au metode cu același nume cu semnături identice. Modificări în biblioteci, mediu de rulare, instrumente.
  • go 1.15 - 11 august 2020 - s-a eliminat suportul pentru variantele de sistem de operare pe 32 de biți pe kernel-ul Darwin, a îmbunătățit performanța linkerului, a adăugat opțiunea de atenuare a vulnerabilității Spectre , s-au adăugat noi avertismente pentru instrumentul go vet. Nu au existat modificări de limbă în această versiune. Până la sfârșitul lunii noiembrie 2020, au existat cinci versiuni minore care remediau erori și vulnerabilități de securitate.
  • go 1.16 - 16 februarie 2021 - S-a adăugat suport pentru ARM pe 64 de biți sub macOS și NetBSD, MIPS64 sub OpenBSD, implementare îmbunătățită pentru o serie de arhitecturi, inclusiv RISC-V. Suportul pentru module este activat în mod implicit, capacitatea de a specifica în mod explicit versiunile a fost adăugată la parametrii comenzii de construire. Nu există schimbări de limbă. Au fost făcute modificări bibliotecilor, în special, a fost adăugat un pachet embedcare implementează capacitatea de a accesa fișierele încorporate în modulul executabil. În iunie 2021, au fost lansate cinci versiuni minore.

Go 2.0

Progres de dezvoltare Din 2017, au fost în curs de pregătire pentru lansarea următoarei versiuni de bază a limbajului, care are simbolul „Go 2.0” [26] . Culegere de comentarii la versiunea actuală și propuneri de transformări, acumulate pe site-ul wiki al proiectului [27] . Inițial, s-a presupus că procesul de pregătire va dura „aproximativ doi ani”, iar unele dintre noile elemente ale limbajului vor fi incluse în următoarele versiuni ale versiunii Go 1 (desigur, doar cele care nu încalcă compatibilitatea cu versiunea anterioară). ). [26] Din aprilie 2021, versiunea 2.0 nu este încă gata, unele dintre modificările planificate sunt în faza de proiectare și implementare. Conform planurilor prezentate în blogul proiectului [28] , lucrările privind implementarea modificărilor planificate vor continua cel puțin în 2021. Inovații sugerate Printre inovațiile fundamentale se numără valorile constante declarate în mod explicit, un nou mecanism de gestionare a erorilor și instrumente de programare generice. Proiectele de inovație sunt disponibile online. Pe 28 august 2018, pe blogul oficial pentru dezvoltatori a fost publicat un videoclip prezentat anterior la conferința Gophercon 2018 , care demonstrează versiunile preliminare ale noului design de gestionare a erorilor și mecanismul funcțiilor generice. Există, de asemenea, multe modificări mai puțin vizibile, dar foarte semnificative planificate, [29] cum ar fi extinderea regulilor de admisibilitate a caracterelor pentru identificatorii în alfabete non-latine, permițând operațiuni de schimbare pentru numere întregi cu semn, folosind liniuța de subliniere ca separator de grupuri de mii în numere, literale binare. Cele mai multe dintre ele sunt deja implementate și disponibile în cele mai recente versiuni ale Go 1. Eroare la procesare Au fost luate în considerare mai multe opțiuni pentru modificarea mecanismului de gestionare a erorilor, în special, un design cu un handler separat de erori („ Manipularea erorilor - Proiectare de proiect ”). Ultima variantă pentru iulie 2019 este descrisă în articolul „ Propunere: O funcție încorporată de verificare a erorilor Go, încercați ”. Această opțiune este cea mai minimalistă și implică adăugarea unei singure funcții încorporate try()care procesează rezultatul unui apel de funcție. Utilizarea sa este ilustrată de pseudocodul de mai jos. func f ( )( r1 type_1 , , rn type_n , err error ) { // Funcție testată // Returnează n+1 rezultate: r1... rn, err of type error. } func g ( )( , err error ) { // Apelați funcția f() cu verificarea erorilor: x1 , x2 , xn = try ( f ( )) // Folosiți încorporat try: // if f( ) returnat non-nul în ultimul rezultat, apoi g() se va termina automat, // returnând aceeași valoare în ultimul rezultat ITS. } func t ( )( , err error ) { // Asemănător cu g() fără a utiliza noua sintaxă: t1 , t2 , tn , te := f ( ) // Apelați f() cu rezultatele stocate în temporar variabile. if te != nil { // Verificați codul de returnare pentru egalitate nil err = te // Dacă codul de returnare nu este zero, atunci este scris în ultimul rezultat al lui t(), return // după care t() se termină imediat. } // Dacă nu a existat nicio eroare, x1 , x2 , xn = t1 , t2 , tn // … variabilele x1…xn își primesc valorile // și execuția t() continuă. } Adică, try()furnizează pur și simplu o verificare a erorilor în apelul la funcția care este verificată și o întoarcere imediată de la funcția curentă cu aceeași eroare. Puteți utiliza mecanismul pentru a gestiona o eroare înainte de a reveni de la funcția curentă defer. Utilizarea try()necesită ca atât funcția care este verificată, cât și funcția în care este apelată să aibă ultima valoare returnată de tip error. Prin urmare, de exemplu, nu puteți main()utiliza try(); la nivelul superior, toate erorile trebuie tratate în mod explicit. Acest mecanism de gestionare a erorilor trebuia să fie inclus în Go 1.14 , dar acest lucru nu a fost făcut. Datele de implementare nu sunt specificate. Cod generic La sfârșitul anului 2018, a fost prezentat un proiect de implementare a tipurilor și funcțiilor generice în Go [30] . La 9 septembrie 2020, a fost publicat un proiect revizuit [31] în care funcțiile, tipurile și parametrii funcției pot fi parametrizați prin tipuri de parametri , care, la rândul lor, sunt controlați de constrângeri . // Stringer este o interfață de constrângere care necesită tipul pentru a implementa // o metodă String care returnează o valoare șir. tip Stringer interface { String () string } // Funcția primește ca intrare un tablou de valori de orice tip care implementează metoda String și returnează // tabloul corespunzătoare de șiruri de caractere obținute prin apelarea metodei String pentru fiecare element al tabloului de intrare. func Stringify [ T Stringer ] ( s [] T ) [] string { // parametrul de tip T, supus constrângerii Stringer, // este tipul de valoare al parametrului de matrice s. ret = make ([] string , len ( s )) for i , v := range s { ret [ i ] = v . String () } return ret } ... v := make ([] MyType ) ... // Pentru a apela o funcție generică, trebuie să specificați un anumit tip s := Stringify [ String ]( v ) Aici, funcția Stringifyconține un parametru de tip T, care este utilizat în descrierea unui parametru obișnuit s. Pentru a apela o astfel de funcție, așa cum se arată în exemplu, trebuie să specificați în apel tipul specific pentru care este apelată. Stringerîn această descriere, este o constrângere care necesită ca MyType să implementeze o Stringmetodă fără parametri care returnează o valoare șir. Acest lucru permite compilatorului să proceseze corect expresia " " v.String(). Implementarea codului generic este anunțată în versiunea 1.18, planificată pentru august 2021. [28]

Implementări

În prezent, există două compilatoare principale Go:

  • gc  este numele generic pentru setul oficial de instrumente de dezvoltare menținut de echipa de dezvoltare a limbii. Inițial a inclus compilatoarele 6g (pentru amd64), 8g (pentru x86), 5g (pentru ARM) și instrumente aferente și a fost scris în C folosind yacc / Bison pentru parser. În versiunea 1.5, tot codul C a fost rescris în Go și assembler, iar compilatoarele individuale au fost înlocuite cu o singură compilare a instrumentului go .
Suportat pentru FreeBSD , OpenBSD , Linux , macOS , Windows , DragonFly BSD , Plan 9 , Solaris , Android , AIX : distribuțiile binare sunt disponibile pentru versiunile curente de FreeBSD , Linux , macOS , Windows , compilarea de la sursă este necesară pentru alte platforme. Dezvoltatorii acceptă o listă limitată de versiuni de platformă, în noile versiuni ale compilatorului excluzând din lista versiunilor acceptate care sunt considerate învechite la momentul lansării. De exemplu, gc 1.12 acceptă Windows 7 și Server 2008R sau o versiune ulterioară.
  • gccgo  este un compilator Go cu o parte client scrisă în C++ și un parser recursiv combinat cu backend-ul standard GCC [32] . Asistența Go a fost disponibilă în GCC începând cu versiunea 4.6 [33] . Majoritatea discrepanțelor cu compilatorul gc sunt legate de biblioteca de rulare și nu sunt vizibile pentru programele Go. [34] Versiunea 8.1 a gcc acceptă toate modificările de limbă până la versiunea 1.10.1 și integrează un colector de gunoi simultan. [35] Threads-urile (go-procedures) sunt implementate în gccgo prin intermediul firelor de execuție OS, drept urmare programele care utilizează în mod activ calculul paralel pot duce la costuri generale semnificativ mai mari. Suportul pentru fluxuri ușoare este posibil folosind linkerul aur, dar nu este disponibil pe toate platformele.

Există și proiecte:

  • llgo  este un strat pentru compilarea Go în llvm , scris în go în sine (a fost în dezvoltare până în 2014) [36] [37] .
  • gollvm  este un proiect de compilare Go through the LLVM compiler system , dezvoltat de Google. Utilizează analizatorul C++ „gofrontend” de la GCCGO și convertorul din vizualizarea gofrontend la LLVM IR [38] [39]
  • SSA interpreter  este un interpret care vă permite să rulați programe go [40] .
  • TinyGo este un compilator Go menit să creeze executabile compacte pentru microcontrolere și WebAssembly folosind LLVM .

Instrumente de dezvoltare

Mediul de dezvoltare Go conține mai multe instrumente de linie de comandă: utilitarul go, care asigură compilarea, testarea și gestionarea pachetelor și utilitarele auxiliare godoc și gofmt, concepute pentru a documenta programe și, respectiv, a format codul sursă conform regulilor standard. Pentru a afișa o listă completă de instrumente, trebuie să apelați utilitarul go fără a specifica argumente. Depanatorul gdb poate fi folosit pentru a depana programe. Dezvoltatorii independenți oferă un număr mare de instrumente și biblioteci concepute pentru a sprijini procesul de dezvoltare, în principal pentru a facilita analiza codului, testarea și depanarea.

În prezent, sunt disponibile două IDE-uri care sunt concentrate inițial pe limbajul Go - acesta este proprietarul GoLand [1] (dezvoltat de JetBrains pe platforma IntelliJ) și LiteIDE gratuit [2] (anterior proiectul se numea GoLangIDE). LiteIDE este un mic shell scris în C++ folosind Qt . Vă permite să compilați, să depanați, să formatați codul, să rulați instrumente. Editorul acceptă evidențierea sintaxei și completarea automată.

Go este susținut și de pluginuri în IDE-urile universale Eclipse, NetBeans, IntelliJ, Komodo, CodeBox IDE, Visual Studio, Zeus și altele. Evidențierea automată, completarea automată a codului Go și rularea utilităților de compilare și procesare a codului sunt implementate ca plugin-uri pentru mai mult de două duzini de editori de text obișnuiți pentru diverse platforme, inclusiv Emacs, Vim, Notepad++, jEdit.

Exemple

Mai jos este un exemplu de „Hello, World!” în limba Go.

pachet principal import "fmt" func main () { fmt . println ( "Bună ziua, lume!" ) }

Un exemplu de implementare a comenzii Unix echo :

pachet principal import ( "os" "flag" // parser linie de comandă ) var omitNewLine = steag . Bool ( "n" , false , "nu tipăriți noua linie" ) const ( Space = " " NewLine = "\n" ) func main () { steag . Parse () // Scanează lista de argumente și setează flags var s șir pentru i := 0 ; i < steag . Narg (); i ++ { dacă i > 0 { s += Spațiu } s += steag . Arg ( i ) } dacă ! * omitNewLine { s += NewLine } os . Stdout . WriteString ( e ) }

Note

Comentarii
  1. Începând cu 2019, nicio implementare a Go nu folosește un colector de gunoi în mișcare.
Surse
  1. Este Go un limbaj orientat pe obiecte? . - „Deși Go are tipuri și metode și permite un stil de programare orientat pe obiecte, nu există o ierarhie a tipurilor.” Preluat la 13 aprilie 2019. Arhivat din original la 3 mai 2020.
  2. Go: cod care crește cu grație . - „Go este orientat pe obiecte, dar nu în modul obișnuit”. Preluat la 24 iunie 2018. Arhivat din original la 18 iunie 2022.
  3. https://go.dev/doc/devel/release#go1.19.minor
  4. 1 2 3 https://golang.org/doc/faq#ancestors
  5. https://talks.golang.org/2015/gophercon-goevolution.slide#19 - 2015.
  6. 1 2 http://golang.org/doc/go_faq.html#ancestors
  7. https://talks.golang.org/2014/hellogophers.slide#21
  8. Google-go-language . Consultat la 28 septembrie 2017. Arhivat din original la 18 ianuarie 2010.
  9. 1 2 3 4 5 6 Întrebări frecvente despre design lingvistic . Consultat la 11 noiembrie 2013. Arhivat din original pe 7 ianuarie 2019.
  10. Noțiuni de bază - Limbajul de programare Go . Consultat la 11 noiembrie 2009. Arhivat din original pe 20 martie 2012.
  11. 1 2 Raportarea unui conflict de nume în instrumentul de urmărire a erorilor . Consultat la 19 octombrie 2017. Arhivat din original la 23 februarie 2018.
  12. 1 2 3 Accesați Google: Language Design in the Service of Software Engineering . talks.golang.org. Preluat la 19 septembrie 2017. Arhivat din original la 25 ianuarie 2021.
  13. Rob Pike. Limbajul de programare Go. golang.org, 30.10.2009. . Consultat la 3 noiembrie 2018. Arhivat din original la 29 august 2017.
  14. când m[-1]înseamnă ultimul element al matricei, m[-2] este al doilea de la sfârșit și așa mai departe
  15. Andrew Gerrand. Amânați, intrați în panică și recuperați pe GoBlog . Preluat la 19 martie 2016. Arhivat din original la 20 aprilie 2014.
  16. SWIG . Consultat la 27 noiembrie 2018. Arhivat din original la 28 noiembrie 2018.
  17. Yager, Will Why Go nu este bine . Consultat la 4 noiembrie 2018. Arhivat din original la 16 iulie 2019.
  18. Elbre, Egon Rezumatul discuțiilor despre Go Generics . Consultat la 4 noiembrie 2018. Arhivat din original la 15 iulie 2019.
  19. Dobronszki, Janos Everyday Hassles in Go . Consultat la 4 noiembrie 2018. Arhivat din original la 10 aprilie 2019.
  20. Fitzpatrick, Brad Go: 90% perfect, 100% din timp . Preluat la 28 ianuarie 2016. Arhivat din original la 3 februarie 2019.
  21. Donovan, 2016 , p. 224-225.
  22. Clasamentul limbajelor de programare 2018: Go și TypeScript au intrat în ligile mari, Kotlin trebuie luat în serios  (rusă) , DOW . Arhivat din original pe 4 august 2020. Preluat la 29 iulie 2018.
  23. Cadru în spirală . Preluat la 23 mai 2020. Arhivat din original la 13 mai 2020.
  24. https://golang.org/doc/devel/release.html Arhivat 17 februarie 2017 în versiunea Wayback Machine Go.
  25. https://golang.org/doc/go1compat Arhivat la 2 octombrie 2017 pe Wayback Machine Go 1 și versiunile viitoare ale Go.
  26. 1 2 Toward Go 2 - Blogul Go . blog.golang.org. Preluat la 29 iulie 2018. Arhivat din original la 26 iunie 2018.
  27. golang/  go . GitHub. Preluat la 29 iulie 2018. Arhivat din original la 29 august 2018.
  28. 1 2 Russ Cox, „Eleven Years of Go” . Preluat la 26 noiembrie 2020. Arhivat din original la 27 noiembrie 2020.
  29. Go2 Iată-ne! . Preluat la 6 decembrie 2018. Arhivat din original la 1 decembrie 2018.
  30. ↑ Contracte - Proiect de proiect  . go.googlesource.com. Preluat la 11 octombrie 2018. Arhivat din original la 11 octombrie 2018.
  31. https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md Arhivat 23 iunie 2020 la Wayback Machine Type Parameters - Draft Design
  32. Go FAQ: Implementare . Consultat la 11 noiembrie 2013. Arhivat din original pe 7 ianuarie 2019.
  33. https://gcc.gnu.org/gcc-4.6/changes.html Arhivat 2 decembrie 2013 la Wayback Machine „Suport pentru limbajul de programare Go a fost adăugat la GCC”.
  34. Configurarea și utilizarea gccgo - Limbajul de programare Go . golang.org. Consultat la 23 noiembrie 2018. Arhivat din original la 23 noiembrie 2018.
  35. GCC 8 Release Series - Changes, New Features, and Fixes - Proiect GNU - Free Software Foundation (FSF  ) . gcc.gnu.org. Preluat la 23 noiembrie 2018. Arhivat din original la 29 noiembrie 2018.
  36. go-llvm Arhivat 11 septembrie 2014 la Wayback Machine ; mutat la llvm-mirror/llgo Arhivat 11 iunie 2018 la Wayback Machine
  37. Copie arhivată . Consultat la 2 noiembrie 2018. Arhivat din original la 22 martie 2017.
  38. gollvm - Git la Google . Preluat la 2 noiembrie 2018. Arhivat din original la 8 decembrie 2018.
  39. Gollvm: Google Working On LLVM-Based Go Compiler  , Phoronix (29 mai 2017) . Arhivat din original pe 12 octombrie 2018. Preluat la 2 noiembrie 2018.
  40. interp - GoDoc . Preluat la 2 noiembrie 2018. Arhivat din original la 29 mai 2019.

Literatură

  • Donovan, Alan A. A., Kernighan, Brian, W. Limbajul de programare Go = Limbajul de programare Go. - M . : SRL „I.D. Williams”, 2016. - P. 432. - ISBN 978-5-8459-2051-5 .
  • Măcelarul M., Farina M. Du-te în practică. - " DMK Press ", 2017. - P. 374. - ISBN 978-5-97060-477-9 .
  • Mark Summerfield. Du-te la programare. Dezvoltarea aplicațiilor secolului XXI. - " DMK Press ", 2013. - P. 580. - ISBN 978-5-94074-854-0 .

Link -uri