Rust — vaihtoehto C++:lle

Rust on viime vuosina noussut kehittäjäpiireissä yhdeksi kuumimmista ja pidetyimmistä uusista koodikielistä. Tästä todisteena on esimerkiksi Stack Overflow:n vuotuisen kehittäjäkyselyn halutuimmat/rakastetuimmat koodikielet -osio, jossa Rust on ollut kärjessä jo liki kymmenen vuotta. Rust on myös murtautumassa enemmän ja enemmän ns. valtavirtaan. Esimerkiksi Linuxin kernel sisältää jo Rustilla kirjoitettuja ajureita ja komponentteja. Myös Google on suosimassa Rustin käyttöä C/C++:n sijasta Androidin ytimen matalimman tason komponenteissa. Yhdysvaltojen asevoimien tutkimusorganisaatio DARPA käynnisti viime kesänä TRACTOR-hankkeen (Translating All C To Rust), jossa päämääränä olisi kääntää kaikki C:llä kirjoitettu legacy-koodi Rustille.

Flat illustration containing the crab mascot of Rust language, and C++ logo.

Rust on monipuolinen ohjelmointikieli

Vaikka Rustin suurimmat hyödyt tulevat esille ohjelmissa, joissa suorituskyky, sovellusten turvallinen monisäikeinen ajo sekä pieni muistijalanjälki ovat elintärkeitä, ja joissa esimerkiksi C ja C++ ovat olleet suosittuja vaihtoehtoja jo viimeiset liki 50 vuotta, on Rustista paljon muuhunkin. Muun muassa JavaScriptin ja Pythonin erilaisten kehittäjätyökalujen kirjoittaminen Rustilla on kasvattanut suosiotaan viime aikoina. Rustin laajan käyttäjäyhteisön ja suoraviivaisen paketinhallinnan myötä on Rustille luotu paketteja ja kirjastoja monenlaisiin käyttötarkoituksiin, aina web-ohjelmoinnista pelimoottoreihin ja työpöytäsovelluksiin. Näistä nostona uusi, nopeudestaan tunnettu, AI-avusteinen ja yhteistyöpohjainen koodieditori Zed, joka on kirjoitettu kokonaan Rustilla.

Rustin valttikortti on muistinhallinta

Rustin yksi keskeisimmistä ominaisuuksista on tehokas ja turvallinen muistinhallinta, joka perustuu muistin omistamisen, lainaamisen ja eliniän tarkkoihin sääntöihin, joita kääntäjä automaattisesti valvoo kääntämisvaiheessa. Omistamisen säännöt pähkinänkuoressa ovat:

  1. Jokaisen arvon omistaa jokin muuttuja.
  2. Jokaisella arvolla voi olla vain yksi omistaja kerrallaan.
  3. Kun arvon omistaja katoaa näkyvistä, arvo pudotetaan.

Esimerkkinä näistä säännöistä kaksi lyhyttä funktiota:

fn rules_one_and_two() {	
	let x: String = String::from("Sääntö 2: Jokaisella arvolla voi olla vain yksi omistaja kerrallaan.");

Luodaan muuttuja x ja annetaan sille arvo, joka on tyyppiä String.

let y = x;

Luodaan muuttuja y ja määritetään sille x :n arvo. String on Rustissa tyyppi, jota ei ole ‘halpa’ kopioida (tällaisia ovat tyypit, joiden koko tiedetään kääntämisvaiheessa kuten erilaiset primitiivityypit bool, i32 , char…) joten y:lle siirretään muuttujan x omistama arvo.

   println!("{x}");
}

Makro nimeltään println! yrittää tulostaa stdout:iin muuttujan x. Tämä kuitenkin epäonnistuu, sillä muuttujaa x ei ole enää olemassa arvonsa poissiirtämisen jälkeen eikä sitä voi enää käyttää.

   fn rule_three() {
	let x = 10;

Määritetään uusi funktio ja sen näkymässä määritetään muuttujalle x arvoksi 10.

{
	 println!("x: {x}");
	 let y = 50;
}

Määritetään kaarisuluilla uusi näkymä, jossa tulostetaan jo tutulla println! -makrolla muuttujan x arvo. Koska muuttuja on määritetty ulommassa näkymässä, se on olemassa myös tässä näkymässä ja tulostus tuottaa stdout:iin tekstin x: 10 . Määritetään tässä uudessa näkymässä myös muuttuja y , annetaan sille arvoksi 50 ja suljetaan näkymä.

      println!("y: {y}");
}

Funktion näkymässä muuttujaa y ei ole olemassa ja tulostus epäonnistuu. Tämä tapahtuu siksi, että sisemmän näkymän päätyttyä y on kadonnut näkyvistä ja sen arvo on pudotettu. Lopulta kun funktion näkymä päättyy, katoaa x näkyvistä ja sen arvo putoaa. Muuttujia voi määrittää globaaleina (yleensä) tiedoston alussa näkymien ulkopuolelle, jolloin ne luonnollisesti näkyvät kaikissa näkymissä (eli funktioissa).

Rustin muuttujat (let x = 42) ovat lähtökohtaisesti muuttumattomia. Muuttuja voidaan merkitä muuttuvaksi (let mut x = 42), jolloin sen arvoa voidaan muokata määrityksen jälkeen, mutta vain mikäli sitä ei ole lainattu. Muuttujia voidaan ‘lainata’ toisiin muuttujiin käyttämällä referenssiä muuttujaan. Referenssejä on kahdenlaisia: muuttumattomia referenssejä (let y = &x) tai muuttuvia referenssejä (let y = &mut x). Muuttujalla voi olla samanaikaisesti joko rajaton määrä muuttumattomia referenssejä TAI tasan yksi muuttuva referenssi. Rustin kääntäjä seuraa lainausten elinaikoja tarkasti, joten viittauksia vapautettuun arvoon ei myöskään voi käyttää. Nämä säännöt tekevät rinnakkaisajon tuomista ongelmista (looking at you data races 👀) historiaa!

Esimerkiksi Javasta tuttua roskankeruuta ei Rustissa myöskään ole. Tämä mahdollistaa C/C++ -tasoisen suorituskyvyn ilman manuaalisen muistinhallinnan tuomia ’metkuja’ (nullpointterien tuoma undefined behaviour, buffer overflow, use-after-free anyone?), jotka satavarmasti ovat jossain välissä ilmaantuneet syvinä juonteina jokaisen C/C++ -kehittäjän otsalle. Mikäli ohjelmassa löytyy halua tai tarvetta sääntöjen rikkomiselle tai manuaaliselle muistinhallinnalle (Rust sisältää työkalut myös älykkäiden pointterien hallintaan), tyypillisesti esimerkiksi muiden kielten kanssa kommunikointiin tai erittäin matalan tason koodin kirjoittamiseen, myös tälle on keinonsa. Koodi voidaan määrittää unsafe -avainsanalla. Unsafe tosin on ehdottomasti viimeinen vaihtoehto, jota ennen tulisi testata tarkkaan, onko muussa koodissa mitään vikaa, sillä muutkaan kääntäjän turvatoimet eivät tässä enää päde ja ohjelmasi virheiden ja kaatumisten riski kasvaa huomattavasti.

Tiukat säännöt luovat kynnyksen Rustin käyttämiselle

Jotta tämä kirjoitus ei menisi vain puhtaaksi ylistyslauluksi, puhutaan myös Rustin huonommistakin (onko niitä oikeasti?) puolista. Näistä suurimpana on oppimiskynnys ja koodin kirjoittamiseen kuluva aika verrattuna muihin, ’perinteisempiin’ koodikieliin. Kääntäjän valvomat omistajuus- ja elinaikasäännöt ovat sangen tiukat, joten ratkaisujen keksiminen kääntäjän miellyttämällä tavalla ei välttämättä ole suoraviivaista. Tämä johtaa myös siihen, ettei pienten, nopeiden koodinpätkien ja ohjelmien kirjoittaminen Rustilla ole kaikista mukavin kokemus. Myös kielen suhteellinen nuoruus kuvastuu kirjastojen kypsyydessä ja standardikirjaston ominaisuuksien laajuudessa. Mielestäni ehkä suurin näistä puutteista on hyvän GUI-kirjaston puute. Kielen nuoruus näkyy myös tekijöiden määrässä, sillä C- ja C++-osaajia on vuosien varrella ehtinyt kertymään jo reilusti, kun taas Rust-osaajia ei vielä niin monia ole. Mikäli Stack Overflow’n käyttäjäkyselystä voisi jotakin päätellä, kysyntä loisi myös tarjontaa tekijöistä, sillä halukkaita koodareita Rustia tekemään päivätyökseen olisi runsaasti.

Vaivannäkö palkitaan toimintavarmuudella

Olemassaolevan C- ja C++-koodin määrä maailmassa on valtava, eikä tätä kaikkea kukaan pysty, osaa tai halua kirjoittaa Rustilla uudestaan. Eikä tälle oikeastaan tarvetta olekaan, sillä vanhemman koodin bugit tulevat (pääsääntöisesti) korjatuksi ajan myötä. Rustin paikka mielestäni onkin juuri uusissa projekteissa, jossa etenkin koodin nopeudella ja muistiturvallisuudella on tärkeä rooli. Vaikka nykyaikainen C++ (etenkin versioista C++11/14/17 eteenpäin) onkin kehittynyt huomattavasti mm. muistinhallinnan (älykkäät pointterit ym.) ja moniajon (atomiset muuttujat, jopa STL sisältää niitä nykyään) saralla, on Rustilla ohjelman kirjoittamisen tuoman lisävaivan vastapainona kuitenkin Rust-kääntäjän takuu siitä, ettei määrittelemätöntä käytöstä tai kaatumisia tapahdu. Tämä vaihtokauppa on mielestäni täysin tekemisen arvoinen diili.

Parhaatkin kehittäjät tekevät varmasti virheitä ja bugeja eksyy jossain vaiheessa kaikkiin ohjelmiin. Miksi emme siis käyttäisi työkalua, joka auttaa havaitsemaan mahdolliset virheet jo koodia kirjoittaessa, eli mahdollisimman aikaisessa vaiheessa? Projektinhallinnan näkökulmasta tämä tulee halvemmaksi kuin bugien havaitseminen koodiarvioinnissa, joka tulee halvemmaksi kuin bugien havaitseminen (julkaisu)testaamisessa, joka taas tulee halvemmaksi kuin bugien havaitseminen tuotannossa…

Rust gud

Kirjoittaja

Olli-Pekka Aro

Olli-Pekka Aro

Software Engineer

Kategoria

Teknologia


+358 40 568 4617


+358 40 568 4617

Scroll to Top