Det har tagit mindre än ett årtionde för dessa två programspråk att etablera sig som ledande alternativ för utveckling av företagssystem – Go, som kommer från Google och Rust från Mozillaprojektet.
Båda språken har funktioner som är oundgängliga i modern systemutveckling: sofistikerade och integrerade verktyg, säker minneshantering, utveckling med öppen källkod och starka användargrupper.
Men bortsett från de likheterna är Rust och Go helt olika. De utvecklades för att klia på olika ställen, för att möta olika krav och för utveckling av olika typer av program.
Därför handlar en jämförelse mellan Rust och Go inte om vilket språk som är ”objektivt bäst”, utan om vilket som är bäst för en given programmeringsuppgift. Med det i bakhuvudet ska vi ta en titt på hur Rust och Go skiljer sig åt, och vad de är bäst lämpade för.
Rust mot Go: Prestanda
På listan över Rusts starka sidor hamnar prestanda i topp tillsammans med säkerhet och enkelhet, kanske allra överst. Rustprogram är utformade för att vara lika snabba, eller nästan lika snabba, som program i C och C++. Det beror på att Rust har abstraktion vid exekvering utan tidspåslag när det gäller minneshantering och bearbetning.
Givetvis kan man skriva långsamma Rustprogram, men du kan ändå vara säker på att Rust inte offrar prestanda för att vinna säkerhet eller bekvämlighet. Det som kostar extra i Rust är att utvecklaren måste satsa tid på att lära sig och behärska språkets abstraktioner för minneshantering. (Mer om minneshantering här nedanför.)
Go, däremot, sänker exekveringshastigheten för att göra livet lättare för utvecklarna. Minneshanteringen sköts av Go vid exekvering (mer här nedanför), så det är oundvikligt att det uppstår ett visst tidspåslag vid exekvering. Men i många sammanhang är den effekten försumbar. Go är i normalfallet flera gånger snabbare än andra lätthanterliga programspråk, som Python.
Kort sagt så är Rust verkligen snabbare, men i de flesta vardagliga sammanhang är skillnaden i exekveringshastighet mellan Rust och Go marginell. Om höga prestanda är ett oeftergivligt krav lämnar Rust Go långt bakom sig.
Rust mot Go: Minneshantering
Minneshanteringen i Rust och Go hänger ihop med de två programmens prestanda. Rust använder en strategi som kallas för ägande (ownership) vid kompilering. Det betyder att nästan alla problem med minneshantering upptäcks innan ett Rustprogram tas i bruk. Om ett Rustprogram inte hanterar minne rätt så går det inte att kompilera. Med tanke på hur mycket i denna värld som vilar på mjukvara av det slag som rutinmässigt visar sig vara osäker, på grund av dåligt minneshantering, är Rusts angreppssätt något som borde ha kommit för länge sedan.
Som jag redan har nämnt betalas denna säkerhet med Rusts mer krävande inlärningskurva. Programmerare måste bemästra Rusts minneshanteringsidiom korrekt, och det kräver tid och övning. Utvecklare som kommer från C, C++, C# och Java måste lära sig tänka på ett nytt sätt när de sätter sig ner med Rust.
Liksom Rust har Go säker minneshantering, men det beror på att minneshanteringen sköts automatiskt vid exekvering. Som programmerare kan du skriva tusentals rader med Go-kod utan att behöva ägna en tanke åt tilldelning och frigörande av minne. Men programmerarna har en viss nivå av styrning av skräpinsamlingen vid körning. Man kan ändra tröskelvärdet för skräpinsamling eller utlösa skräpinsamling manuellt för att förhindra att skräpinsamlingscyklerna går i vägen för driften.
Programmerare kan också utföra en del manuell minneshantering i Go, men språket är med avsikt gjort för att det ska vara svårt. Du kan till exempel använda pekare för att accessa variabler, men du kan inte använda pekararitmetik för att komma åt godtyckligt valda arean i minnet, såvida du inte använde paketet unsafe – inget bra val för program som ska användas i produktion.
Om den aktuella programmeringsuppgiften kräver att du allokerar och frigör minne manuellt – till exempel för hårdvara på låg nivå eller där det finns krav på högsta möjliga prestanda – är Rust gjort för att klara det. Men det betyder inte att Gos automatiska minneshantering innebär att det inte skulle kunna skapa robust mjukvara för din uppgift.
Rust mot Go: Utvecklingshastighet
Ibland är det viktigare att utvecklingen går snabbt än att programmet blir snabbt. Python har gjort succé trots att det är långt ifrån det snabbaste språket vid exekvering, men för att det är ett av de snabbaste språken att skriva program i. Go har samma fördel. Det är rakt på sak och enkelt, vilket gör att man kan utveckla snabbt. Kompilering går snabbt, och Gos körbara kod är snabbare än Pythons kod (liksom koden från andra interpreterade, utvecklarvänliga språk) med flera storleksordningar.
Kort sagt ger Go både enkelhet och snabbhet. Så vad är haken? Några funktioner som finns i andra språk (till exempel generiska datatyper) har utelämnats för att språket ska bli lättare att lära sig, lättare att bemästra och lättare att underhålla. Avigsidan är att en del programmeringsuppgifter inte går att utföra utan att man upprepar stora stycken kod.
Rust har flera funktioner än Go, och det tar längre tid att lära sig och bemästra det. Rusts kompileringstider brukar dessutom vara längre än för motsvarande Go-program, särskilt för applikationer med omfattande beroendeträd. Och så är det fortfarande, trots att Rust-projektet har gjort stora ansträngningar att korta kompileringstiderna.
Så om det viktiga är en kort utvecklingscykel och att kunna engagera folk snabbt i ett projekt är Go det bättre valet. Men om tidsåtgången inte är lika viktig, däremot minnessäkerhet och exekveringshastighet, välj Rust.
Rust mot Go: Samtidighet och parallelism
Modern maskinvara har många kärnor, och moderna applikationer är nätverksbaserade och distribuerade. Språk som inte är tänkta för sådant hänger inte med. Programmerare måste kunna köra uppgifter oberoende av varandra, vare sig det är i en tråd eller flera trådar, och de måste kunna dela tillstånd mellan uppgifter utan att riskera datakorruption. Både Rust och Go har sätt att klara detta.
Samtidighet är inbakat i Gos syntax sedan starten i form av goroutines (lättviktstrådar) och kanaler (kommunikationsmekanismer för goroutines). Dessa primitiva datatyper gör det enkelt att skriva applikationer (till exempel nätverkstjänster) som måste kunna hantera många uppgifter samtidigt utan att riskera sådana problem som race conditions. Go gör inte race conditions omöjliga, men det har inbyggda testmekanismer som varnar programmeraren för race conditions som kan uppstå vid programkörning.
Rust har nyligen fått en inbyggd syntax för samtidighet i form av nyckelorden async/.await. Innan samtidighet med async/.await,fanns kunde man hantera samtidighet med en ”crate” eller ett paket för Rust vid namn futures. Även om Rusts samtidighet inte, till skillnad från Gos samtidighet, inte bygger på åratal av utvecklarerfarenhet så ärver den Rusts försprång när det gäller minneshantering. Det betyder att om Rust-kod skulle kunna orsaka race conditions så kompilerar den helt enkelt inte. Samtidiga eller asynkrona operationer är svårare att skriva i Rust på grund av syntaxreglerna, men de kommer att vara mer robusta på sikt.
Rust mot Go: Interoperabilitet med ärvd kod
Nya språk som Rust och Go är inriktade på säker minneshantering och bekväm programmering på sätt som de som skapade äldre programspråk inte kunde föreställa sig. Men det nya måste alltid kunna fungera tillsammans med det gamla. Därför kan både Rust och Go fungera ihop med kod skriven i C, fast med olika begränsningar.
Rust kan prata direkt med C-bibliotek genom att använda nyckelordet extern och paketet libc (Rust kallar paket för crates), men alla anrop till C-bibliotek måste taggas som osäkra. Med andra ord: Rust kan inte garantera att deras minneshantering eller deras trådar är säkra. Du måste manuellt förvissa dig om att gränssnitt till C-kod är säkra. Anrop till C++-kod finns det å andra sidan inte stöd för alls (inte ännu), såvida koden inte har ett C-kompatibelt gränssnitt. Exempel på hur man inkorporerar kod med Rusts FFI (Foreign function interface) finns, för C och andra språk, på webbplatsen Rust FFI Omnibus website.
Go har paketet cgo för arbete med C. Paketet cgo gör att du kan anropa C-bibliotek, använda C-headerfiler i din Go-kod och att konvertera vanliga datatyper från C till Go (till exempel strängar). Men med tanke på att Go har minneshantering och skräpinsamling måste du förvissa dig om att alla pekare som du skickar till C hanteras korrekt.
Kort sagt är Rust en aning snällare när det gäller interoperabilitet med C än vad Go är, så projekt med stark koppling till redan existerande C-kod kan föredra Rust. För båda språken gäller att interoperabilitet med C kostar på för programmeraren: extra tänkande, långsammare kompilering, mer komplexa verktyg, svårare debugging.
Kom ihåg att både Rust och Go alltid kan samverka med annan kod på högre nivåer – som att utväxla data över nätverkssocklar eller namngivna pipar i stället för funktionsanrop – men den möjligheten används på bekostnad av hastighet.
Rust mot Go: Summering
Valet mellan Rust och Go för ett utvecklingsprojekt är främst en fråga om vilket språk som passar bäst för just det projektet. Det kan sammanfattas så här:
Rusts fördelar:
- Korrekthet vid exekvering (vanliga fel kompilerar helt enkelt inte)
- Exekveringshastighet i toppklass
- Säker minneshantering utan skräpinsamling
- Kod på hårdvarunivå
Gos fördelar:
- Snabb utvecklingscykel
- Snabb exekvering
- Säker minneshantering med skräpinsamling
- Enkelt för utvecklarna
- Lättfattlig programkod