1Selitä, miten Go-kielen nolla-arvot toimivat sisäänrakennetuille ja viittauksen kaltaisille tyypeille, ja miksi niillä on merkitystä, kun muuttujia esitellään ilman eksplisiittistä alustusta.
Go-kielessä muuttuja, joka on esitelty ilman eksplisiittistä alustusta, alustetaan automaattisesti tyyppinsä nolla-arvoon. Numeerisista tyypeistä tulee `0`, `bool`-tyypistä `false`, `string`-tyypistä `""`, ja taulukot (arrays) tai tietueet (`struct`) nollataan alkio alkiolta tai kenttä kentältä. Osoittimien tai viittausten kaltaisilla tyypeillä, kuten osoittimilla, `slice`- ja `map`-tyypeillä, kanavilla, funktioilla ja rajapinnoilla, on `nil` nolla-arvonaan. Tällä on merkitystä, koska Go-muuttujat ja pois jätetyt `struct`-kentät aloittavat deterministisessä tilassa roskadatan sijaan. Lisäksi monet API-rajapinnat on suunniteltu siten, että nolla-arvo on hyödyllinen oletusarvo, vaikka jotkin `nil`-arvot vaativat yhä alustuksen ennen tiettyjä toimenpiteitä.
2Miten Go käsittelee struct-tyyppien yhtäsuuruutta, ja mitä tapahtuu, kun struct sisältää kenttiä, jotka eivät ole vertailtavia?
Gossa `struct`-arvoja voidaan vertailla operaattoreilla `==` ja `!=` vain silloin, kun rakenteen jokainen kenttä on vertailtava. Yhtäsuuruus vertailee vastaavia kenttiä käyttäen kunkin kentän omaa vertailusääntöä. Jos `struct` sisältää kentän, joka ei ole vertailtava (kuten `slice`, `map` tai funktio), koko `struct`-tyyppi ei ole vertailtava, ja kahden kyseisen tyyppisen arvon vertaileminen `==`-operaattorilla aiheuttaa käännösaikaisen virheen. Tällaisten `struct`-tyyppien kohdalla on käytettävä mukautettua vertailulogiikkaa tai sopivaa syvävertailun (deep equality) apufunktiota, erityisesti testeissä.
3Selitä merkkijonojen muuttumattomuus (string immutability) Go-kielessä ja suhde string-, []byte-, bytes.Buffer- ja strings.Builder-tyyppien välillä.
Go-kielen `string` on muuttumaton tavujono, joka on usein UTF-8-muotoista tekstiä, mutta sen ei ole pakko olla validia UTF-8:aa. Merkkijonoa ei voi muokata paikallaan; sen sisällön muuttamiseksi se tyypillisesti muunnetaan `[]byte`-tyypiksi tavutason muokkauksia varten tai `[]rune`-tyypiksi koodipisteiden (code point) muokkauksia varten, ja sen jälkeen takaisin merkkijonoksi. Normaalit muunnokset `string`- ja `[]byte`-tyyppien välillä kopioivat dataa ja saattavat varata muistia, joten toistuvat muunnokset tai toistuva merkkijonojen yhdistäminen silmukoissa voivat olla raskaita. `strings.Builder` on optimoitu merkkijonojen tehokkaaseen rakentamiseen, kun taas `bytes.Buffer` on muokattava tavupuskuri, joka on hyödyllinen tavupohjaiselle datalle sekä I/O-operaatioille, ja josta voi myös tuottaa merkkijonon.
4Miten osoittimet toimivat Go-kielessä, ja mitkä operaatiot on tarkoituksellisesti kielletty verrattuna C-kieleen?
Go:n osoitin on tyypitetty arvo, joka sisältää toisen arvon osoitteen, esimerkiksi `*int`, joka osoittaa `int`-arvoon. Käytä merkintää `&x` arvon osoitteen hakemiseen ja `*p` osoittimen purkamiseen (dereference), kun haluat lukea tai kirjoittaa osoitettua arvoa. Osoittimen välittäminen tai tallentaminen mahdollistaa saman taustalla olevan arvon havainnoinnin tai muokkaamisen useasta paikasta käsin, ja osoitin voi olla `nil`. Toisin kuin C-kielessä, turvallinen Go kieltää tarkoituksellisesti osoitinaritmetiikan ja mielivaltaisen raakaosoitteiden käsittelyn; matalan tason poikkeustapaukset vaativat `unsafe`-paketin käyttöä.
5Selitä, miten vakiot ja `iota` toimivat Gossa, mukaan lukien tyypitetyt ja tyypittämättömät vakiot.
Gon vakiot ovat käännösaikaisia arvoja: totuusarvoja, merkkijonoja tai numeerisia arvoja. Tyypitetyllä vakiolla on tietty tyyppi. Tyypittämättömällä vakiolla ei ole kiinteää konkreettista tyyppiä ennen kuin sitä käytetään tyypitetyssä kontekstissa, ja numeeriset tyypittämättömät vakiot esitetään siihen asti täydellä tarkkuudella. Arvon on oltava esitettävissä valitussa tyypissä. `iota` on ennalta määritelty tunniste, jota käytetään `const`-määrittelyissä: se alkaa nollasta jokaisessa `const`-lohkossa ja kasvaa jokaisen vakiomäärittelyn myötä, mikä tekee siitä hyödyllisen enum-tyyppisille vakioille ja bittilipuille.
6Miten eksplisiittiset tyyppimuunnokset toimivat Gossa, ja miksi kieli välttää monia implisiittisiä muunnoksia?
Go vaatii yleensä eksplisiittisiä muunnoksia käyttäen syntaksia `T(x)` arvon tyyppiä muutettaessa, esimerkiksi `int64(i)` tai `MyID(n)`. Tämä pitää tyyppimuutokset näkyvinä ja välttää yllättävät implisiittiset numeeriset, totuusarvojen tai merkkijonojen muunnokset. Muunnokset ovat sallittuja vain Gon muunnossääntöjen mukaisesti, ja ne voivat muuttaa arvoa esimerkiksi kokonaisluvun ylivuodon (overflow) tai katkaisemisen (truncation) takia, tai johtuen liukuluvun muuttamisesta kokonaisluvuksi nollaa kohti katkaisten tai tarkkuuden menetyksestä. Tyypittömät vakiot (untyped constants) ovat joustavampia: niitä voidaan käyttää tyypitetyssä kontekstissa, jos vakion arvo on esitettävissä kyseisessä tyypissä.
7Kuvaile eroa Go-kielen taulukoiden ja `slice`-tyyppien välillä, mukaan lukien miten pituus, kapasiteetti ja taustalla oleva tallennustila käyttäytyvät.
Go-kielen taulukoilla on kiinteä pituus, joka on osa sen tyyppiä, kuten `[3]int`. Se tallentaa alkionsa suoraan, ja taulukon sijoittaminen muuttujaan tai välittäminen funktiolle kopioi koko taulukon arvon. `slice`, kuten `[]int`, on pieni kuvaaja taustalla olevasta taulukosta: käsitteellisesti se sisältää osoittimen alkioihin, pituuden ja kapasiteetin. `slice`-tyypin pituus on näkyvillä olevien alkioiden määrä; sen kapasiteetti määrittää, kuinka monta alkiota voidaan käyttää `slice`-alkukohdasta taustataulukon loppuun mennessä. `slice`-tyypit ovat joustavia: arvon viipalointi uudelleen (reslicing) muuttaa kuvaajaa, ja `append` voi käyttää uudelleen samaa taustataulukkoa, jos kapasiteetti sallii, tai varata uuden, jos se ei riitä.
8Kuinka Go:n `map`-tyyppi käyttäytyy suhteessa avaintyyppeihin, puuttuviin avaimiin, `nil`-arvoisiin `map`-rakenteisiin ja iteroinnin järjestykseen?
Go:n `map`-rakenteen avaintyyppien on oltava vertailukelpoisia (comparable); `slice`-, `map`- ja funktiotyyppejä ei voi käyttää suoraan avaimina. Puuttuvan avaimen hakeminen palauttaa alkiotyypin nolla-arvon, joten puuttuminen ja olemassa oleva nolla-arvo erotetaan toisistaan `comma-ok`-idiomilla (`v, ok := m[k]`). `nil`-arvoista `map`-rakennetta voi lukea ja sen voi käydä läpi `range`-rakenteella, mutta siihen sijoittaminen aiheuttaa paniikin (`panic`); se on siis alustettava ennen kirjoittamista. `map`-rakenteen iterointijärjestystä ei ole määritelty, eikä koodin tulisi luottaa siihen.
9Milloin Go:ssa `struct` tai muu arvo pitäisi välittää parametrina arvona (by value) ja milloin osoittimena (by pointer)?
Välitä parametrina arvona, kun arvo on pieni, se on vain luettavana kyseisessä kutsussa tai kun haluat itsenäisen kopion semantiikan. Välitä osoittimena, kun funktion täytyy muuttaa kutsujan arvoa, kopiointi olisi liian kallista suorituskyvyn kannalta, tyyppiä ei tulisi kopioida lainkaan tai jos nolla-arvo (`nil`) tai jaettu identiteetti on osa API-rajapintaa. Älä oleta automaattisesti, että osoittimet ovat aina nopeampia: ne voivat lisätä aliasointia, keosta varaamista (heap escapes), GC:n (Garbage Collector) työmäärää ja heikentää välimuistin käyttötehokkuutta. Suosi ensisijaisesti selkeää semantiikkaa ja tee suorituskykymittaus suorituskyvyn kannalta kriittisille valinnoille.
10Miten sijoitus ja kopiointi toimivat Go-kielen `struct`-, taulukko- (array), `map`- ja `slice`-tyypeillä?
Sijoitus Go-kielessä kopioi sijoitettavan arvon, mutta vaikutus riippuu tyypistä. `struct`-tyypin sijoitus kopioi tietueen kentät, ja taulukon sijoitus kopioi jokaisen alkion. `slice`-tyypin sijoitus kopioi otsikkotiedot – osoittimen, pituuden ja kapasiteetin – joten `slice`-arvot jakavat yleensä saman taustataulukon. `map`-tyypin sijoitus kopioi viittauksen kaltaisen kuvaajan, joten molemmat muuttujat viittaavat samaan `map`-dataan. Riippumatonta omistajuutta varten on tehtävä puolustava kopio, kuten käyttämällä `copy`-funktiota tai `slices.Clone`-funktiota `slice`-arvoille, ja luomalla uusi `map` sekä kopioimalla alkiot `map`-arvoille.
11Miten sulkeumat sieppaavat muuttujia Go-kielessä, ja mitä bugeja voi ilmetä silmukoiden ja goroutinejen yhteydessä?
Sulkeuma on Go-kielessä funktioarvo, joka viittaa sitä ympäröivän leksikaalisen näkyvyysalueen muuttujiin. Se sieppaa itse muuttujat, joten sulkeuma pystyy havaitsemaan niihin tehdyt muutokset, ja siepatut muuttujat voivat elää kauemmin kuin ne luonut funktio, jos sulkeuma karkaa näkyvyysalueensa ulkopuolelle. Silmukoihin ja goroutineihin liittyviä bugeja ilmenee, kun sulkeumat jakavat tai tarkastelevat muuttujaa sen muuttumisen jälkeen. Tämä johtaa usein siihen, että goroutinet tai viivästetyt takaisinkutsut näkevät väärän arvon tai aiheuttavat datakilpailuja jaetussa tilassa. Yleinen korjaus on välittää haluttu arvo argumenttina sulkeumalle tai luoda uusi paikallinen kopio jokaisella iteraatiolla, sekä synkronoida samanaikainen pääsy tarvittaessa. Go 1.22 -versiosta lähtien `for`- ja `range`-silmukoiden esittelemät muuttujat ovat iteraatiokohtaisia. Tämä poistaa monia klassisia silmukkamuuttujien sieppaamiseen liittyviä bugeja, mutta silmukan ulkopuolella uudelleenkäytetyt muuttujat ja muu jaettu muuttuva tila voivat silti aiheuttaa ongelmia.
12Kuvaile `range`-avainsanan semantiikka `array`-, `slice`-, `map`-, `string`- ja kanavatyyppien (channel) kanssa.
`range` käy läpi alkioita operandin tyypin mukaisesti. `array`- tai `slice`-tyypin kanssa se palauttaa indeksin ja alkion arvon. Alkion arvo on kopio, joten siihen sijoittaminen ei muokkaa alkuperäistä kokoelmaa. `array`-arvon läpikäynti kopioi taulukon iteraatiota varten, kun taas `slice`-arvon läpikäynti käyttää `slice`:n otsikkoa (slice header) ja indeksoi sen alla olevaa taulukkoa. `map`-tyypin kohdalla se palauttaa avaimen ja arvon määrittelemättömässä järjestyksessä, ja palautettu arvo on kopio. `string`-tyypin kanssa se palauttaa tavuindeksin sekä dekoodatun Unicode-koodipisteen (`rune`), ei `rune`-merkin sijainti-indeksiä. Kanavan kanssa se vastaanottaa arvoja, kunnes kanava suljetaan ja tyhjennetään. `nil`-kanavan läpikäynti jää odottamaan ikuisesti.
13Mitä eroa on arvo- ja osoitinvastaanottajilla, ja miten vastaanottajan valinta vaikuttaa muutettavuuteen, kopiointiin ja rajapintojen toteuttamiseen?
Arvovastaanottajametodi saa kopion vastaanottajasta, joten se sopii vain luku -tyyppiseen toimintaan ja pienille, muuttumattomille tyypeille; muutokset vastaanottajan kopioon eivät näy kutsujalle. Osoitinvastaanottajametodi saa kopion vastaanottajaan osoittavasta osoittimesta, joten se voi muuttaa alkuperäistä arvoa ja välttää suurten tai sellaisten arvojen kopioinnin, joita ei pitäisi kopioida. Vastaanottajan valinta vaikuttaa metodijoukkoihin (`method set`): arvovastaanottajalliset metodit kuuluvat sekä `T`:n että `*T`:n metodijoukkoon, kun taas osoitinvastaanottajalliset metodit kuuluvat vain `*T`:n metodijoukkoon. Siksi rajapinnan, joka vaatii osoitinvastaanottajametodia, toteuttaa `*T`, ei `T`, vaikka osoitteen sisältävä `T`-arvo voikin kutsua metodia käyttämällä tavallista metodikutsun syntaksia.
14Kuvaile, miten rajapinnat toimivat Go-kielessä ja mitä tarkoittaa, että tyyppi toteuttaa rajapinnan implisiittisesti.
Go-kielen rajapinta määrittelee joukon vaadittuja metodeja. Konkreettinen tyyppi toteuttaa rajapinnan implisiittisesti, kun sen `method set` sisältää kyseiset metodit; erillistä toteutusmäärittelyä ei ole. Rajapintamuuttuja voi pitää sisällään konkreettisen dynaamisen arvon, jonka tyyppi toteuttaa rajapinnan, ja rajapinnan metodikutsut ohjataan kyseisen dynaamisen arvon toteutukseen. Tyhjä rajapinta, joka kirjoitetaan `interface{}` tai yleensä `any`, ei sisällä vaadittuja metodeja, joten kaikki tyypit toteuttavat sen. Se, toteuttaako tyyppi `T`, `*T` vai molemmat tietyn rajapinnan, riippuu niiden `method set` -määritelmistä ja vastaanottimen (receiver) valinnoista.
15Miten tyyppiväitteet (type assertions) ja tyyppikytkimet (type switches) toimivat rajapinta-arvojen kanssa?
Tyyppiväite tarkistaa rajapintaan tallennetun dynaamisen arvon: `v := x.(T)` onnistuu, jos rajapinta-arvon dynaaminen tyyppi on `T`, tai jos `T` on rajapinta, jonka dynaaminen arvo toteuttaa. Yhden tuloksen muoto aiheuttaa paniikin (`panic`), jos se epäonnistuu. `comma-ok`-muoto, `v, ok := x.(T)`, ilmoittaa onnistumisesta panikoimatta. Tyyppikytkin, `switch v := x.(type)`, haarautuu rajapinta-arvon dynaamisen tyypin perusteella, jolloin `v` saa tyyppinsä täsmäävän tapauksen mukaan.