Skriv ut

IoT har blivit det främsta tillämpningsområdet för det nya språket Rust, som erbjuder en kombination av ett modernt programmeringsspråk, bra prestanda och hög säkerhet. Enligt en färsk undersökning är Rust det snabbast växande programmeringsspråket.

Rust skapades med målet att få ett modernt programmeringsspråk som löser många av de problem som finns i äldre språk som C och C++. Själva namnet är en sammandragning av ordet ”robust” och en av grundidéerna är just att eliminera potentiella fel redan vid kompileringen. Framför allt handlar det om fel som rör minneshanteringen (se faktaruta). Detta är viktigt – enligt Microsoft står fel som har med minneshanteringen att göra för cirka 70 procent av alla säkerhetskritiska fel.

Säkerheten i Rust ­kommer varken på bekostnad av prestanda eller storleken på det kompilerade programmet. Till skillnad från dynamiska språk som Javascript och Python, eller språk som kompileras till en virtuell maskin som Java, så kompileras Rust-program direkt till maskinkod på samma sätt som C-program. I praktiken är C och Rust ganska likvärdiga prestandamässigt, vilket gör att Rust kan användas för program som av prestanda- eller utrymmesskäl annars skulle ha skrivits i C. Men det innehåller också funktioner som gör det användbart i situationer där man annars skulle ha använt högnivåspråk som Go, Java eller Javascript, till exempel som back-end i webb­tillämpningar. Många anser därför att Rust har potential att bli ett brett använt språk.

Rust kom ut i sin första stabila version (1.0) 2015 och har sedan 2016 varje år toppat programmerar-communityt Stackoverflows årliga enkätfråga om “most loved language”. Språket kom från början från Mozilla Foundation och utvecklingen drivs nu som ­öppen källkod av Rust Foundation, som i sin tur stöds av en rad stora teknikföretag som ­Amazon, Google och Microsoft.

Enligt undersökningen ”Developer Nation” från Slashdata är Rust det språk som ökat mest i användning de senaste två åren, från 0,6 miljoner till 2,2 miljoner användare i början av 2022. Enligt undersökningen är Rust mest använt för tillämpningar inom IoT samt Augmented Reality och Virtual Reality (AR/VR).

Det faktum att gruppen användare växer är viktigt för att ett språk ska nå framgång. Om det finns många användare kommer det också att finnas många bibliotek och anpassningar för verktyg, som i sin tur gör det lättare att använda språket. Men inte minst är det också viktigt att det finns utvecklare som kan språket, om företag ska våga satsa på att utveckla större system i det. Man vill inte bli sittande med ett system som är skrivet i ett aldrig så fiffigt språk om det inte går att rekrytera kunniga utvecklare.

Att använda Rust för IoT-tillämpningar är också naturligt: inte bara är språket lika snabbt som C, utan det är också nästan lika minnessnålt, vilket är viktigt speciellt i små system med begränsade resurser. Eftersom det både blir dyrt och svårt att rätta fel när koden väl kommit ut i en produkt som finns hos slutkonsumenten är det viktigt att felen är få.

Rust i Linux-kärnan
Just eftersom Rust både är maskinnära och minnessäkert passar det bra i en operativsystemskärna. Linux-kärnan har experimentellt stöd för att skriva kod i Rust. I juni i år sa Linux-grundaren Linus Torvalds att Rust-stöd kommer i den reguljära versionen av Linux, troligen i produktionsversionen av Linux 6.0 av Linuxkärnan, som troligen kommer ut under hösten 2022.

Microsoft har undersökt möjligheterna att använda Rust i delar av Windows. För några år sedan meddelade man att man börjat använda Rust i en icke-namngiven del av Windows, och att det hade fungerat väl. Microsofts utvecklingsverktyg Visual Studio Code har också stöd för Rust.

Bland realtidsoperationsystem (RTOS) är stödet för Rust fortfarande begränsat. Enligt sajten Are We RTOS yet finns det 12 RTOS-projekt som påbörjat stöd för Rust, varav de flesta befinner sig i en väldigt tidig utvecklingsfas.

Certifierade system
Om ett affärssystem fallerar kan kostnaderna bli stora. Men om det blir fel i ett system för flyg, självkörande fordon eller medicinsk teknik kan människor dö. För den typen av system ställs därför mycket högre krav. Beroende på hur känslig tillämpningen är kan man behöva certifiera hela kedjan från insamling av krav till leverans av färdig produkt. Ett viktigt steg i detta är att certifiera kompilatorn.

Idag finns det ingen certifierad kompilator för Rust, men ­Ferrous Systems har börjat utveckla Ferrocene, som har en fullt certifierad Rust-kompilator som mål. Ferrous Systems har under 2022 fått med Adacore i det samarbetet. Adacore utvecklar bland annat Ada-kompilatorer och målet är att mot slutet av 2022 få Rust att uppfylla standarden ASIL-D, en säkerhetsklassning av system för fordon.

Rust i praktiken
Hur fungerar det att använda Rust i ett befintligt system? Vi har pratat med en senior ingenjör vid ett internationellt teknikföretag i Stockholm:

– Vi har ett stort system som av prestandaskäl huvudsakligen är skrivet i C/C++. Att skriva om det i Rust skulle säkert ha stora fördelar, men det skulle vara svårt att skriva om huvudprogrammet. Eftersom Rust använder ownership-modellen för att hantera minnet (se faktaruta) kräver det att man definierar vilka delar av systemet som har tillgång till vilka data. Detta gör att man i sin tur i praktiken är tvungen att kompilera om hela programmet varje gång man gör en ändring någonstans i källkoden. Eftersom vårt system har mer än en miljon rader kod skulle detta leda till väldigt långa kompileringscykler.

Alternativ till Rust

Go, som utvecklas av Google, skapades till viss del för att lösa problem i C och C++, framför allt när det gäller parallella system, samtidigt som det har bättre prestanda jämfört med många dynamiska programmeringsspråk. Det har blivit populärt till exempel för backends i webbsystem. Men det har inte samma prestanda som Rust och lämpar sig inte för inbyggda system eller systemnivåprogrammering.

Redan i mitten av 1970-talet utlyste amerikanska försvarsdepartementet Dod en tävling om ett nytt programmeringsspråk som skulle vara bättre för att skriva korrekta program. Resultatet blev Ada och sedan 1991 har Dod ställt krav på att alla system man köper in ska vara Ada-baserade. Många trodde att Ada skulle ta över som generellt standardspråk, men utanför tillämpningar med formella krav på säkerhetscertifiering har Ada haft begränsad framgång.

Programmeringsspråket D är ett försök att vidareutveckla C++ till att bli säkrare. Språket har bland annat en delmängd, SafeD, som ska garantera att vissa typer av minnesfel inte inträffar. Språket D har dock aldrig fått någon större spridning.

– Alternativet är att man för varje modul som ska kunna kompileras separat definierar ett tydligt gränssnitt. Men det skulle kräva väldigt mycket arbete. Så även om vårt system i princip skulle lämpa sig för Rust, så är det mer troligt att det kommer att dyka upp delsystem och moduler, som i sin tur anropas av ett huvudprogram skrivet i C/C++.

Kommunikationsbiblioteket Curl, som används i en rad olika operativsystem, och som från början är skrivet i C har delvis blivit omskrivet i Rust. Huvudutvecklaren Daniel Stenberg gav tidigare i år ett föredrag där han beskrev erfarenheter med det. Enligt Daniel Stenberg har man framgångsrikt bytt ut en delkomponent åt gången och de delar som är skrivna i Rust fungerar bra. Utmaningarna har varit att det dels saknats dokumentation, och dels att det saknats grundläggande bibliotek i Rust för funktioner man behövt. Ofta har Curl-projektet varit den första användaren av nya Rust-komponenter.

Det senare bekräftas av Nicolae Paladi, VD för Canarybit som utvecklar en webbplattform för att dela konfidentiella data.

– En crate (en modul i Rust) kan ändras väldigt snabbt eller till och med försvinna över en natt. Gränssnittet kan också förändras i grunden i nästa versionen av modulen. Däremot ser vi detta som ett tillfälligt problem, som kommer att försvinna i takt med att språket utvecklas och adoptionen ökar.

Artikeln är tidigare publicerad i magasinet Elektroniktidningen.
Prenumerera kostnadsfritt!

En annan åsikt som ofta hörs är att det tar längre tid att komma igång med Rust än med andra språk. Dels ser språket annorlunda ut än språk som C eller Python, dels finns det mycket att sätta sig in i och förstå innan man kan bli produktiv.

The Embedded Rust Book
En bok om Rust för inbyggnadsprogrammering; gratis nedladdning här.

 

Vanliga programmeringsfel som Rust eliminerar

När det gäller att hantera minnet i ett programmeringsspråk har det hittills funnits två olika sätt att hantera minnet: antingen gör man det manuellt, som i C/C++, eller så gör man det automatiskt med hjälp av en metod kallad ”garbage collection”.

Garbage collection är en funktion där runtime-systemet återvinner minne som inte längre används, utan att programmeraren har direkt kontroll över det. Detta är praktiskt, just eftersom programmeraren inte behöver veta något om vad som händer med minnet.

Under förutsättningen att själva garbage collectorn inte ­innehåller fel, så gör garbage collection att man eliminerar många typer av minnesfel. Detta används i de flesta högnivåspråk som Go, Java, Java­script och Python. Men eftersom programmeraren oftast inte kan påverka när garbage collection sker, innebär det också att system när som helst kan stanna upp för att göra garbage collection. Detta är alltså inte ett egentligt fel, men det kan skapa problem framför allt i realtidssystem.

Om stoppet är någon sekund i ett webbsystem spelar det inte så stor roll. Men samma fördröjning i till exempel krockkudde kan få dödlig utgång. Om man har ett system med krav på korta svarstider måste man därför antingen överdimensionera det, så att en allt för lång garbage collection i praktiken aldrig kan inträffa, eller skriva systemet i ett programmeringsspråk som inte använder garbage collection.

Hittills har detta i praktiken inneburit att man skriver i C/C++ och hanterar minnet manuellt. Men det är väldigt lätt att göra fel när man hanterar minnet manuellt. Dessutom är det ofta svårt att återskapa minnesfel, eftersom de kan bero på tillfälligheter, till exempel vad som råkade ligga i minnet när systemet startade eller i vilken ordning olika funktioner körs, vilket programmeraren oftast inte kan styra över, eftersom operativsystemet sköter detta. Man pratar då om Heisenbugs (efter kvantfysikern Werner Heisenberg) – fel som bara uppstår ibland och på ett till synes slumpmässigt sätt.

Rust eliminerar bland annat fel av följande typer:

Buffer overflow: när programmet skriver utanför det område en variabel tilldelats. Om till exempel ett användarnamn får vara 20 tecken långt och programmet får ett användarnamn som istället är 25 tecken långt och inte kontrollerar längden, så kommer fem tecken att hamna utanför variabeln och alltså skriva över en del av minnet. En så kallad buffer overflow-attack är ett vanligt sätt att bryta sig in i ett datorsystem.

Buffer over-read: snarlikt buffer overflow, men innebär istället att programmet läser längre än det skulle.

Minnesläckage: I ett program som kan reservera minne under exekveringen, till exempel genom att använda pekare, så behöver man hålla reda på vilka delar av minnet som är tilldelade (allokerade) till någon del av programmet och vilka som är tillgängliga. När man slutar använda en sådan variabel måste den del av minnet den tog upp läggas till till det tillgängliga minnet. Minnesläckage uppstår när detta inte fungerar, och minnet som inte längre används fortfarande är markerat som om det är allokerat. Ett system som är drabbat av minnesläckage kommer förr eller senare få slut på minne och krascha; detta är ett av skälen till att en återstart av ett system ofta får det att fungera igen.

Oinitialiserat minne: När programmet läser från minne som inte fått något värde tilldelat. Det värde man läser blir då det som råkar stå i minnet.

Race conditions: I ett program med flera trådar eller processer kan programmet bete sig olika beroende på i vilken ordning trådarna/processerna körs. Ett exempel är när två olika delar av programmet samtidigt försöka skriva till samma del av minnet.

Rust introducerar en ny modell för att hantera minnet: ownership. Ownership innebär att man för varje variabel i systemet talar om var i programmet variabeln får användas (”valid scope”) och om det är tillåtet att tilldela värden till variabeln, eller om man bara får avläsa värdet. Det senare kallas ”access type”. Grundregeln är att man antingen kan ändra en variabel på ett ställe, eller läsa på flera ställen. Men inte både och. Vid kompileringen räknar kompilatorn ut livstiden för värdena och automatiser den manuella minneshanteringen.

Ownership kombinerar snabbheten i den manuella minneshanteringen från C/C++ med säkerheten hos ett system med garbage collection. Detta gör att man eliminerar många typer av fel, men nackdelen är att det kan vara omständligt att definiera ownership korrekt.

Har man gjort det fel kommer programmet helt enkelt inte att kunna kompileras. Med andra ord: fler fel upptäcks vid kompileringstillfället. Det innebär också att när man väl lyckats kompilera programmet, så är sannolikheten större att det faktiskt fungerar korrekt, vilket i sin tur gör testfasen enklare.