A kifejezések egy elvégzendő számítást határoznak meg. A C++-hoz hasonlóan a Go-ban is nagy szerep jut a kifejezéseknek. A C++-szal ellentétben azonban itt nem követték a "minden legyen kifejezés" elvet, néhány olyan megszorítással éltek, mely a nyelvet biztonságosabbá teszi.
Az összetett literálok építenek értékeket rekordoknak, tömböknek, szeleteknek, és mapeknek. Minden egyes kiértékeléskor egy teljesen új értéket hoznak létre.
Például vegyük az alábbi egyszerű pont típust:
Ennek többféle módon adhatunk értéket: az értékeke megfelelő sorrendű felsorolásával, és kulcs-érték párokkal is.
Tömböket és szeleteket is hasonló módon inicializálhatunk. Kulcsos megadásnál a kulcsoknak mindenképpen számoknak kell lenniük. Itt már lehet keverni a kulcsos és a kulcs nélküli megadást, a kulcs nélkül megadott elemek mindig az előzőnél eggyel nagyobb indexűek lesznek. Ha egy tömbnél nem akarjuk megadni az elemszámot, akkor ...-tal helyettesíthetjük. Például az alábbi három megadás ekvivalens:
Ha a méret helyére nem írunk semmit, szeletet kapunk:
Mapet a tömb indexes változatához hasonló formában készíthetünk, de itt feltétlenül ki kell írnunk minden kulcsértéket:
Ha egy összetett literálon belül további összetett literálok vannak, és ezek típusa megegyezik a külső literál elemtípusával, a belső literálok típusát nem kell kiírni. Például a következő két kifejezés ugyan azt a két dimenziós szeletet adja.
A func szóval nem csak a fájl fő részében, valamint a típusdefinícióknál találkozhatunk, hanem kifejezésként is használható. Ekkor segítségével egy névtelen függvényt készíthetünk.
Ezek a függvénykifejezések zárványok (closure): hozzáférhetnek a bezáró függvény változóihoz, azok megosztásra kerülnek közöttük. Az ily módon több helyről elérhető változók addig élnek, amíg valahonnan elérhetők.
Alapkifejezésekből építhetünk operátorokkal bonyolultabb kifejezéseket. Példák alapkifejezésekre:
A szelektort a . jelzi, és egy struct egy elemét választja ki, vagy egy (a változó típusán értelmezett) metódust hív meg. A pointerek egy szintet automatikusan feloldódnak a szelektor kifejezés hatására.
Tömbökből, szeletekből, stringekből és map-ekből is indexeléssel választhatjuk ki az i. elemét.
a T típusú elemekből álló tömb/szelet, akkor
0 és len(a)-1 között kell lennie, különben futásidejű hibát kapunk.a string, akkor
byte típusú, a string i. bájtja0 és len(a)-1 között kell lennie, különben futásidejű hibát kapunka[i]-t nem módosíthatjuk (pl. nem adhatunk neki értéket).a map típusú, K típusú kulccsal és E típusú értékkel, akkor
nil értékű vagy nem tartalmaz i-hez rendelt elemet, akkor az eredmény E típus alapértékev, ok = a[x], ekkor v a hozzárendelt elem (vagy alapérték), ok pedig egy logikai érték, amely azt jelzi, hogy volt-e hozzárendelt elem.A tömbök és a szeletek is tudják a határaikat, és a kiválasztás előtt mindig történik határellenőrzés.
Szeleteket képezhetünk tömbökből, stringekből és más szeletekből. Ezt az alábbi formában tehetjük meg:
A fenti kifejezés a tömbből/szeletből/stringből választja ki a lo-tól hi indexig található elemeket. Az eredményül kapott szelet indexelése 0-tól kezdődik, és hi - lo hosszan tart. Azaz az első kiválasztott elem a lo pozíción lévő, az utolsó pedig a hi - 1 pozíción lévő.
A szeletelés egyik, vagy akár mindkét paramétere elhagyható, ekkor ezek szélsőértékekként értelmeződnek: azaz ha lo-t hagyjuk el, az alsó határ 0 lesz, míg ha hi-t hagyjuk el, a felső határ len(a)-val fog megegyezni.
Stringek szeletelésekor stringet kapunk (tehát a kapott érték is immutable). Ha tömböt vagy szeletet szeletelünk, szeletet kapunk, mely az eredeti tömbbel közös memórián osztozik, a szelet elemeinek változtatása az eredeti tömb elemeinek változását is magával vonja és viszont.
k <= cap(t) cap(t[i:j:k]) = k - ik <= j - k
Lehetőségünk van arra, hogy a program egy adott pontján ellenőrizzük, hogy egy változó dinamikus típusa megfelelő-e számunkra. Ha a egy interface típusú érték, akkor:
kifejezés ellenőrzi, hogy a típusa jó-e. Ha T nem egy interfész, akkor pontos egyezést vizsgál T-vel, ha T egy interfész, akkor azt vizsgálja, hogy a dinamikus típusa megvalósítja-e azt az interfészt.
Ha a típusa valóban megfelel a fentieknek, akkor a kifejezés eredménye a, de immáron T típussal. Egyébként (pl. akkor is, ha a értéke nil) futásidejű hibát kapunk. Azaz habár a típusa nem ismert fordítási időben, de egy helyes programtól elvárjuk, hogy T típusú legyen.
Egy gyengébb megkötést enged az alábbi forma:
Ekkor ok egy logikai változó lesz, mely tartalmazza, hogy az értékadás sikeres volt-e, v-nek értékül tudtuk-e adni x-et. Ha ez mégsem teljesül, akkor v értéke T alapértéke lesz.
Az operátorok az operandusaikat kifejezésekké kombinálják. Az operandusok (az összehasonlító operátorok esetének és a biteltolásnak a kivételével) azonos típusúaknak kell lenniük. Ha egy típusos kifejezésre és egy nemtípusos konstansra alkalmazunk operátort, akkor a konstans (ha ez lehetséges), a másik operandus típusára konvertálódik.
Az operátorok precedenciája az alábbi:
Az azonos szinten levő operátorok balról jobbra értékelődnek ki. A ++ és a -- nem operátorok, hanem utasítások, így gyakorlatilag ezek kötnek a leggyengébben. Szintén utasítás a csatornára való adatküldés (->). A kiértékelés sorrendje zárójelezéssel a szokásos módon befolyásolható.
Az aritmetikai operátorok numerikus típusokon értelmezettek, eredményük az első operandus típusával megegyező típusú. A négy alapművelet (+, -, *, / minden numerikus típuson értelmezett; a + operátor stringek konkatenációjára is alkalmas. A többi aritmetikai operátor csak egészekre használható.
Egészek között az osztás (/) eredménye egész, a nulla felé csonkolva (kivételt képez, ha az osztandó x a típusában ábrázolható legkisebb negatív szám: ekkor x / -1 == x, ill. x % -1 == 0). Nullával való osztás futásidejű hibát okoz.
Az osztásnak a maradékképzés (%) operátorral az alábbi kapcsolata áll fenn:
A biteltolás operátorok bal operandusuk bitjeit tolják el a jobb operandus által megadott mértékben. Ha a bal operandus előjeles egész, akkor aritmetikai eltolást végeznek (tehát az előjelet nem befolyásolják), míg előjel nélküli bal operandus esetén logikai eltolást hajtanak végre.
Az eltolás mértékének felső korlátja nincs: n-nel való eltolás azonos n-szeri 1-gyel való eltolással az adott irányban.
Előjel nélküli egészekre a +, -, *, << operátorok modulo 2^n értelmezettek (n az ábrázoláshoz használt bitek száma), azaz túl- vagy alulcsordulás esetén az értékek "körbejárnak" (wrap around).
Előjeles egészekre ugyanezen operátorok esetén a túlcsordulás megengedett, hiba nem keletkezik és az eredménynek determinisztikusnak kell lennie. További megkötés a fordítók számára, hogy nem optimalizálhatnak a túlcsordulás lehetőségét kizárva, pl. nem tehetik fel, hogy x < x + 1 mindig igaz lesz.
Az összehasonlító operátorok két értéket hasonlítanak össze, eredményük logikai érték. Az összehasonlítás során az első operandus értékül adható kell legyen a második operandusnak, vagy fordítva.
Az ==, != operátorok operandusainak összehasonlíthatónak kell lenniük. A összehasonlítható típusok:
nilnil értékkel (pointerek, csatornák és interface-ek ugyancsak összehasonlíthatók a nil-lel).
A rendezés operátorok operandusaitól a nyelv megköveteli a rendezhetőséget. A rendezhető típusok:
A logikai operátorok (&&, ||, !) logikai értékeken értelmezettek a szokásos módon. A jobb oldali operandus csak szükség esetén (lustán) értékelődik ki.
Két pointer-operátor használható a nyelvben:
Címet kérni címezhető objektumtól (változó, pointer dereferencia, szelet-indexelés kifejezés, rekord mezője, címezhető tömb indexelése), illetve összetett literáltól lehet. Utóbbi esetben a literál által meghatározott új objektum létrejön és a kifejezés értéke ezen objektumra mutató pointer lesz.
nil pointer feloldásának kísérlete futásidejű hibát okoz.
A fogadó operátor unáris, operandusa egy fogadásra (is) alkalmas csatorna, eredménye pedig a csatornáról fogadott érték. A kifejezés blokkol, amíg nincs a csatornáról elérhető érték (így nil csatornáról való fogadás örökre blokkol). Példák:
A fogadó kifejezés speciális formája a következő: x, ok := <-ch. Ebben a formában az ok változó logikai típusú és azt adja meg, hogy a fogadott x érték a csatornáról érkezett-e (ekkor ok igaz), vagy alapérték (ha a csatorna zárva van és üres).
Függvényhívás a szokásos formában történik, a függvényt eredményező kifejezés (pl. függvénynév vagy literál) után zárójelben felsoroljuk a paramétereket:
Az f függvény aktuális paramétereinek értékül adhatónak kell lennie a formális paramétereinek. A függvény paraméterei a hívás előtt, pontosan egyszer értékelődnek ki. A visszatérési érték(ek) típusa a függvény visszatérési típusának (típusainak) megfelelő.
nil értékű függvénykifejezés hívása futásidejű hibát eredményez.
Ha egy g függvény visszatérési értékei (bár többen vannak) számban és típusban megegyeznek egy f függvény paramétereivel, akkor engedélyezett az f ( g ( /* paraméterek */ ) ) egyszerűsített függvényhívás.
... paraméterek
Speciális eset a ... paraméter használata: ha egy függvény utolsó paramétere params ...T alakú, azt a következő két formában használhatjuk:
Ha T egy olyan típus, amely rendelkezik M metódussal
ezt a metódust hívja meg a megfelelő paraméterekkel. A paraméterekre és a visszatérési értékre a függvényekkel azonos szabályok vonatkoznak.
Metódusok deklarálásáról lásd a Függvény- és metódusdeklarációk szakaszt.
func (t *T) F(a int) int definíciójú függvény a következő függvényszignatúrának felel meg: func(t *T, a int) int és akár meg is hívható ilyen formában, a következő két hívás ekvivalens: t.F(1) és T.F(t,1)
Az 1.1-es verzióval vezették be a nyelv készítői a metódus értéket, ezzel egy típus adott értékéhez lerögzített függvényt kaphatunk.
A müködését ez a kódrészlet szemlélteti:
DistanceFromOrigo változó értéke egy függvény, ami a Distance metódus egy specializált példánya, mely mindig az origótól való távolságot adja meg, szignatúrája sem tartalmazza már paraméterként a Point-ot, amin meghívtuk a metódust: func(p2 Point) float64.
A típuskonverziók Pascal-szerű formában történnek:
Ha a típus operátorral kezdődik, akkor zárójelbe kell tenni, pl.
A T(x) konverzió az alábbi esetekben sikeres:
A számok közötti és a szám-string konverzióban felléphet futásidejű költség, illetve pontosságvesztés; a többi konverzió csak az adott változó típusát változtatja meg, reprezentációját nem.
Pointer és egész számok közötti konverzió nem engedélyezett.
A numerikus típusok közötti konverzióra az alábbi szabályok vonatkoznak:
Egész érték stringgé konvertálása esetén a string egyetlen karaktere az egész számnak megfelelő Unicode kódpont lesz - ha nincs megfelelő kódpont, akkor a \uFFFD érték.
Ha bájt-szeletet ([]byte) konvertálunk stringgé, a szelet bájtjainak megfelelő karakterek rendre a string bájtjai lesznek. nil szelet konverziója üres stringet eredményez.
[]rune típus konverziója során az egyes rune értékek string-alakjának konkatenációja az eredmény. Ha a szelet nil, az eredmény üres string.
Stringet []byte típusra konvertálva az eredmény a string egyes bájtjainak sorozata. Ha a string üres, az eredmény []byte(nil).
Stringet []rune-ra konvertálva a string unicode kódpontjainak szekvenciáját kapjuk eredményül (üres string esetén []rune(nil) értéket).
A konstans kifejezésekben csak konstans részkifejezéseket és operátorokat használhatunk, és még fordítási időben kiértékelődnek. A kiértékelés mindig pontos, nincs információveszteség még akkor sem, ha a részeredmények tárolásához az ábrázolható típusoknál jelentősen nagyobb tárterületre van szükség (pl. const Huge = 1 << 1000).
A konstans kifejezésekben a típuskonverziókat szabadabban kezeli a nyelv.
Nemtípusos konstansok szabadon használhatók a konstans kifejezésekben ott, ahol a megfelelő típusos kifejezések használhatók. Bináris operátort alkalmazva két, különböző fajta nemtípusos konstansra, a művelet jellege és az eredmény típusa a két fajta közül az alábbi listában később elhelyezkedő fajtájú lesz: egész, karakter, lebegőpontos, komplex. Például ha egy nemtípusos egész konstans osztunk egy nemtípusos komplex konstanssal, akkor az eredmény egy (nemtípusos) komplex konstans lesz.
Konstans összehasonlító kifejezés mindig nemtípusos logikai konstanst eredményez.
Ha a konstans biteltolás kifejezés bal operandusa nemtípusos konstans, akkor az eredmény mindig (nemtípusos) egész konstans - különben a bal operandusnak megfelelő típusú (a biteltolás operátor bal operandusa mindig egész típusú).
Minden, a fentiektől eltérő esetben, mikor két nemtípusos konstanson alkalmazunk operátort, az eredmény azonos fajtájú nemtípusos konstans lesz.Implementációs megszorítás: egyes fordítók a nemtípusos lebegőpontos és komplex konstans kifejezések kiértékelésénél kerekíthetnek. Ez akár azzal a hatással is járhat, hogy egy (végtelen pontosság esetén) egész értékű lebegőpontos konstans nem lesz használható egészként. Pl. az alábbi egy változó értéke nem biztos, hogy pontosan 1 lesz:
A típusos konstansok értékétől a nyelv megköveteli, hogy az pontosan ábrázolható legyen a megadott típussal. Így például az alábbiak fordítás hibát okoznak:
iota konstans kifejezés
A speciális iota kifejezés egész (nemtípusos) konstansok sorozatának előállítását könnyíti meg. Kezdeti értéke 0, és minden konstans változó deklarációjánál, amelyben felhasználjuk (ld. Konstans-deklaráció), eggyel nő. Ha a fordító egy új const kulcsszót talál, az értéke újra 0 lesz.
Példa az iota használatára:
A nyelv nem tartalmaz felsorolástípust, viszont az iota szerepelhet konstans kifejezésben és ezek a kifejezések implicit ismételhetőek egy const blokon belül, így elérhető hasonló viselkedés:
Egy értékadás vagy kifejezés elemei közt minden funkció- illetve metódushívás valamint kommunikáció balról jobbra értékelődik ki.
A fenti értékadásban ez a következő sorrendet jelenti: f(), h(), i(), j(), <-c, g(), végül k(). Viszont a fentiekhez képest az x-nek és az y-nak, illetve az indexelések kiértékelésének sorrendjére nincs megkötés.