Rust-ohjelmointikieli – C:n ja C++:n korvaaja?

Törmäsimme sattumalta jokin aika sitten uudehkoon ohjelmointikieleen nimeltä Rust. Aluksi se vaikutti liian hyvältä ollakseen totta, joten päätimme ottaa selvää miten asia on. Kielen kehittäjät antoivat suuria lupauksia siitä millainen Rust on ja mitä sillä pystyy tekemään. Ehdottomasti suurin väite oli että Rust on turvallisempi ja yhtä suorituskykyinen kuin C ja C++. Rustin ominaisuuksien ja kielen suunnitteluperiaatteiden luvattiin korjaavan lähes kaikki näiden kielien ongelmat.

Tämä kirjoitus on suunnattu teknisesti orientoituneille johtajille, projektipäälliköille sekä ohjelmistoarkkitehdeille. Kirjoituksen lähdeteoksena on käytetty The Rust Programming Language -kirjaa, ellei toisin ole mainittu. (https://doc.rust-lang.org/book/)

Ferris

Ferris-rapu (http://rustacean.net) on Rust-ohjelmointikielen epävirallinen maskotti, joka seikkailee myös The Rust Programming Language -kirjassa.

C ja C++

C ja C++ ovat järjestelmäohjelmointikieliä. Niillä pystytään tekemään ohjelmistoja sulautetuista järjestelmistä, joissa on tiukat reaaliaikavaatimukset, aina raskaisiin työpöytäohjelmistoihin, joiden pitää pystyä prosessoimaan monimutkaisia operaatioita. Webin puolelle C ja C++ eivät ole vielä juuri levinneet, koska niiden turvallinen käyttö vaatii merkittävästi erityisosaamista. Nämä kielet mahdollistavat hyvin erikoisten ja vaikeasti selvitettävien ongelmien syntymisen, jos niitä käyttävät ohjelmoijat eivät ole tietoisia niihin liittyvistä vaaroista ja eivät jatkuvasti arvioi tekemäänsä koodia näiden vaarojen suhteen. Tällaiset vaarat liittyvät esimerkiksi muistinhallintaan ja rinnakkaisuuteen.

Rustin historia ja nykytila

Rust-ohjelmointikielen kehitys alkoi vuonna 2006 Mozilla-yhteisössä, joka kehittää myös Firefox-selainta. Rust on käännettävä ohjelmointikieli, samaan tapaan kuin C ja C++. Rust kehitettiin alunperin korjaamaan Firefox-selaimessa olevia rinnakkaistushaasteita. C++-kielellä kirjoitetun Firefox-selaimen toiminnan jatkuvasti lisääntyvä rinnakkaisuus oli muodostunut niin haastavaksi kokonaisuudeksi, että parhaaksi ratkaisuksi nähtiin kehittää kokonaan uusi ohjelmointikieli tekemään rinnakkaistuksesta yksinkertaisempaa ja turvallisempaa. Rustin kehitys levisi hyvin nopeasti myös Mozillan ulkopuolelle, ja nykyisin Rust-kielen kehittäjistä suurin osa on Mozillan ulkopuolella. Kielen ympärille on kehittynyt kukoistava ekosysteemi.

Nykyään Rust-kieli on jo verrattain laajasti teollisuudessa käytössä ollakseen vielä niin tuore keksintö. Kielen tuotantokäytössä olevia referenssejä on esitelty kielen nettisivuilla (https://www.rust-lang.org). Mainittakoon esimerkiksi että Dropbox-pilvitiedostopalvelu (https://www.wired.com/2016/03/epic-story-dropboxs-exodus-amazon-cloud-empire) ja NPM-paketinhallintajärjestelmä (https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf) käyttävät Rustia.

Tärkeimmät tekniset hyödyt ja haasteet

Kuten aiemmin mainittiin Rust korjaa C ja C++-kielien pahimmat ongelmat, ja voisi potentiaalisesti jopa korvata ne kokonaan. Nämä kielet tosin ovat niin laajasti käytössä etteivät ne kokonaisuudessaan ole mihinkään katoamassa.

Rust tarjoaa turvallisen muistinhallinnan ilman ajonaikaista roskienkeruuta (garbage collection). Rust käyttää niin sanottua omistajuusmallia, jonka avulla huolehditaan siitä että muistissa oleva data on olemassa sen ajan kun sitä tarvitaan, ja sen jälkeen varattu muisti vapautetaan automaattisesti. Tällä mallilla ehkäistään sekä muistin korruptoituminen (memory corruption), jossa muistissa oleva data ei olekaan mitä sen pitäisi olla, sekä muistivuodot (memory leak), jossa muistissa oleva data jää vapauttamatta ja pahimmassa tapauksessa käytettävissä oleva muisti loppuu kesken.

Rust tarjoaa turvallisen rinnakkaisuuden estämällä kilpailutilanteet (race condition) ja jaetun muistin korruptoitumisen. Omistajuus- ja lainausmallien yllättävänä hyötynä on myös rinnakkaisuusongelmien eliminoituminen. Lainausmalli määrittää miten muistissa olevaan dataan voidaan viitata. Nämä kieleen sisäänrakennetut mekanismit estävät tilanteen jossa yksi säie ylikirjoittaa toisen tekemät muutokset jaettuun muistiin – molemmat eivät voi kirjoittaa samanaikaisesti samaan dataan rikkomatta lainaussääntöjä, joiden noudattamista kääntäjä valvoo automaattisesti.

 

Questioning Ferris
Ferris ihmettelee miksei koodi käänny.


Rust on tehokas. Rust vertautuu tehokkuudeltaan C ja C++ -kieliin, tietyissä tapauksissa ollen jopa nopeampi kuin C++. Rust käyttää niin sanottua Zero Cost Abstraction -mallia, jolloin korkean tason kielistä tuttujen ominaisuuksien käyttämisestä ei seuraa ajoaikaista tehokkuushaittaa, vaan käännöksen yhteydessä ohjelmointityötä helpottava abstraktio eliminoituu pois. Jotkin ominaisuudet, kuten dynaaminen sitominen, täytyy tehdä ajonaikaisesti, mutta näitä on pyritty välttämään ja ohjelmoija voi aina valita haluaako käyttää tällaisia ominaisuuksia. Rustin suurin heikkous on tällä hetkellä hitaahko käännösaika, joka kuitenkin vertautuu C++-ohjelmaan jossa on käytetty useita templateja.

Rust tarjoaa matalan tason kielen hallinnan korkean tason kielen käyttömukavuudella. Rust on erittäin ilmaisuvoimainen ja siihen on kerätty syntaksiltaan parhaat ominaisuudet, joita muut kielet tarjoavat. Kielen syntaksi muistuttaa monelta osin C++11/14:ta, mutta siinä on myös lisäksi uusimmista Javascript versioista (ES6) tuttuja mekanismeja. Rustin ilmaisuvoimaisuuden ja helppokäyttöisyyden puolesta puhuu se, että kielellä on kehitetty jo useampi web-ohjelmistokehys. Rustin soveltaminen webin puolella on jo toisen kirjoituksen arvoinen kokonaisuus. Nyt mainittakoon että Rust tuo mielenkiintoisia mahdollisuuksia myös webin puolelle, Full Stack voi tästä eteenpäin tarkoittaakin jopa koko polkua käyttöjärjestelmästä ja sen ajureista verkkoselaimessa näkyvään web frontendiin.

Merkittävin Rust-ohjelmointia omalta osaltaan hidastava syntaksiero muihin ohjelmointikieliin nähden on Rustissa olevat elinaikamerkinnät (lifetime annotations). Rust-kääntäjä analysoi muistissa olevan datan olemassaoloajat käännösaikana, mutta se ei kaikissa tilanteissa pysty päättelemään onko jokin data olemassa riittävän pitkään. Tämän vuoksi ohjelmoija joutuu välillä opastamaan kääntäjää merkitsemällä elinaikamerkinnöillä muuttujien olemassaoloaikoja. Esimerkiksi jos funktio palauttaa siivun merkkijonosta tai taulukosta, on tuo siivu viittaus merkkijonon osaan. Tällaisessa tilanteessa kääntäjä voi tarvita ohjeistuksen, että alkuperäisen merkkijonon täytyy olla olemassa yhtä kauan tai pidempään kuin siivu, joka on viittaus sen osaan. Rust-kääntäjä pystyy nykyisellään jo päättelemään monia tällaisia tilanteita, ja vastaavia myös lisätään jatkossa, jolloin ohjelmoijan tekemien elinaikamerkintöjen määrä vähenee kääntäjän kehittyessä.

Cargo-paketinhallintajärjestelmä tuo Web-kehityksestä tuttuja työkaluja järjestelmätasolle. Perinteisesti C ja C++ ohjelmien ulkoisten kirjastojen hallinta on ollut hankalaa ja kääntämisen avuksi on tarvittu cmake tai vastaava työkalu. Cargo toimii samaan tapaan kuin NPM webbimaailmassa, ja hoitaa vielä samalla cmake:n tehtävät automatisoimalla käännöksen tekeminen. Lisäksi Cargossa on sisäänrakennettu dokumentaatiotyökalu, jolla saa tuotettua projektille dokumentaatioon suoraan lähdekoodissa olevien dokumentaatiokommenttien perusteella, samaan tapaan kuin doxygenillä ja vastaavilla työkaluilla.

Rust-ohjelmakoodissa voidaan käyttää olemassa olevia C-kirjastoja FFI-rajapinnan kautta (Foreign Function Interface). Tämä auttaa hyödyntämään valtavaa määrää olemassa olevaa toiminnallisuutta, eikä kirjastoja tarvitse kääntää Rust-kielelle. Samaan tapaan Rust-kirjastoja voidaan käyttää muissa ohjelmointikielissä FFI-rajapinnan kautta. Tukea C++-kirjastojen käyttämiselle Rustissa ei vielä ole, koska Rustin ja C++:n käyttämät abstraktiot eivät ole suoraan yhteensopivia keskenään.

Rust mahdollistaa myös sen turvamekanismeista poikkeamisen tarvittaessa. Unsafe-avainsanalla voidaan tehdä koodilohkoja, joissa voidaan nimensä mukaisesti suorittaa epäturvallista koodia. Tämä on olennainen ominaisuus, joka mahdollistaa liitynnät epäturvalliseen maailmaan Rustin ulkopuolella, kuten laitteistoon. Tämän mekanismin haittana on kuitenkin se että Rust-koodiin voidaan tuoda vahingossa samoja ongelmia kuin C ja C++ -kielissä, mutta nämä vaaranpaikat on kuitenkin onneksi aina helppo löytää Unsafe-lohkoista.

Unsafe Ferris

Ferris ei tykkää Unsafe-koodilohkoista.

Liiketoimintahyödyt

Microsoft julkaisi Helmikuussa 2019 tutkimuksen, jonka mukaan 70% kaikista Microsoft tuotteiden tietoturva-aukoista johtuu muistinhallintaan liittyvistä ongelmista (https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues). Juuri siis niistä ongelmista, jotka Rust ratkaisee ilmaiseksi.

Tässä on syytä huomata ettei kyse ole pelkästään potentiaalisista tietoturva-aukoista, vaan kyse on täysin todellisesta ajasta joka käytetään debuggaamiseen, eli ohjelmointivirheiden tutkimiseen. Muistinhallinnasta ja rinnakkaisuudesta aiheutuvat ongelmat ovat häijyimpiä ongelmia, mitä ohjelmistokehityksessä voi tulla vastaan. Nämä ongelmat eivät ole aina ilmeisiä vaan ne ilmenevät satunnaisesti ja satunnaisilla tavoilla. Sanomattakin on selvää että tällaisten epämääräisten, mutta todella vakavien, ongelmien korjaaminen vie aivan suunnattomasti aikaa. Useamman kerran on allekirjoittaneelle C++-kielen kanssa tullut tilanne vastaan, jossa on tarvinnut käyttää useampi viikko puhtaasti tällaisen virheen selvittämiseen. Korjaus yleensä hoituu muuttamalla paria koodiriviä, mutta tällaisen ongelman syyn selvittäminen on tuskaista. Rust on suunniteltu siten, että tältä työltä vältytään kokonaisuudessaan, eikä siitä hyödystä tarvitse maksaa esimerkiksi hitaammalla koodintuotto- tai ohjelman suoritusnopeudella.

Refaktoroidako kaikki mahdollinen?

Käänteentekevä uusi teknologia herättää aina myös kysymyksen kannattaako nykyjärjestelmiä korvata uudella teknologialla. Yleisohjeena voidaan sanoa, että hyvin toimivia ja järjellisellä työllä ylläpidettävissä olevia järjestelmiä ei kannata uudelleenkirjoittaa. Älä korjaa, jos se ei ole rikki. Sen sijaan uudet ominaisuudet kannattaa kehittää uusilla teknologioilla ja rikkinäisten vanhojen järjestelmien korvaamista kannattaa vakavasti harkita.

Yksi Rustin vahvuuksista on se että sillä voidaan korvata yksittäisiä osia muilla ohjelmointikielillä kirjoitetuista ohjelmistoista FFI-rajapinnan avulla (Foreign Function Interface). Rustilla kirjoitettu komponentti liitetään osaksi muuta ohjelmistoa, joka tarvittaessa kutsuu Rust-komponentin funktioita suoraan toisen ohjelmointikielen puolelta. Tällöin kysymys ei ole siitä että koko olemassa oleva järjestelmä tarvitsisi kirjoittaa uusiksi jotta Rustin tarjoamiin hyötyihin päästään käsiksi, vaan käyttöönotto on yksinkertaista ja turvallista.

Yhteenveto

Rust on hyvin mielenkiintoinen verrattain uusi ohjelmointikieli, jolla on hyvä potentiaali korvata C ja C++ -kielien käyttö erityisesti uudessa kehityksessä ja vanhojen järjestelmien jatkokehityksessä. Rust tarjoaa monenlaisia moderneja etuja vanhoihin ja vaarallisiin kieliin verrattuna.

Happy Ferris(http://rustacean.net)