liksom många utvecklare har jag varit intresserad av Rust under ganska lång tid. Inte bara för att det förekommer i så många rubriker på Hacker News, eller på grund av det nya tillvägagångssättet tar språket till säkerhet och prestanda, men också för att människor verkar prata om det med en viss känsla av kärlek och beundran. Dessutom är Rust av särskilt intresse för mig eftersom det delar några av samma mål och funktioner i mitt favorit go-to-Språk: Swift. Eftersom jag nyligen har tagit mig tid att prova Rust i några små personliga projekt, ville jag ta lite tid att dokumentera mina intryck av språket, särskilt i hur det jämförs med Swift.

The Big Picture

Rust och Swift har många saker gemensamt: de är båda sammanställda språk med kraftfulla, moderna typsystem och fokus på säkerhet. Funktioner som algebraiska typer och förstklassig hantering av valfria värden hjälper till att flytta många klasser av fel från körning till kompileringstid på båda dessa språk.

så hur skiljer sig dessa språk? Det bästa sättet jag kan karakterisera skillnaden är:

Swift gör det enkelt att skriva säker kod.
Rost gör det svårt att skriva osäker kod.

dessa två uttalanden kan låta likvärdiga, men det finns en viktig skillnad. Båda språken har verktyg för att uppnå säkerhet, men de gör olika avvägningar för att uppnå det: Swift prioriterar ergonomi på bekostnad av prestanda, medan Rust prioriterar prestanda på bekostnad av ergonomi.

avvägningen: Prestanda vs ergonomi

det största sättet som denna skillnad i prioritet demonstreras är i det tillvägagångssätt som dessa språk har för minneshantering. Jag börjar med Rust eftersom språkets inställning till minneshantering är en av dess unika försäljningsställen.

i Rust hanteras minnet primärt statiskt (ja det finns andra lägen för minneshantering som referensräkning, men vi ignorerar dem för tillfället). Vad detta betyder är att Rust-kompilatorn analyserar ditt program och bestämmer enligt en uppsättning regler när minnet ska tilldelas och släppas.

för att leverera säkerhet använder Rust en ny strategi som kallas lånekontroll. Hur detta fungerar i praktiken är att du som programmerare måste ange om referensen är muterbar eller oföränderlig varje gång du passerar runt en variabel (dvs. en hänvisning till en minnesplats). Kompilatorn använder sedan en uppsättning regler för att säkerställa att du inte kan mutera en enda bit Minne på två ställen samtidigt, vilket gör det bevisbart att ditt program inte har data raser.

detta tillvägagångssätt har några mycket fördelaktiga egenskaper med avseende på minnesanvändning och prestanda. Lånekontroll kan vara mycket parsimonious med minne, eftersom det i allmänhet undviker kopieringsvärden. Det undviker också prestanda overhead av en lösning som sophämtning, eftersom arbetet görs vid kompileringstid snarare än runtime.

men det kommer med vissa nackdelar när det gäller användarvänlighet. På grund av äganderätten i Rust finns det några designmönster som helt enkelt inte fungerar i Rust. Det är till exempel inte trivialt att implementera något som en dubbelt länkad lista eller en global variabel. Detta blir sannolikt mer intuitivt med tiden, och det finns lösningar för dessa problem, men Rust ställer verkligen begränsningar på programmeraren som inte finns på andra språk.

även om det inte så ofta talas om som Rust, har Swift också en intressant historia när det gäller minneshantering.

Swift har två grundläggande typer av variabler: referenstyper och värdetyper. I allmänhet är referenstyper heap-allokerade och hanteras genom referensräkning. Detta innebär att vid körning spåras antalet referenser till ett referensräkat objekt och objektet avallokeras när räkningen når noll. Referensräkning i Swift är alltid Atom: det betyder att varje gång en referensräkning ändras måste det finnas en synkronisering mellan alla CPU-trådar. Detta har fördelen att eliminera möjligheten att en referens felaktigt frigörs i en flertrådad applikation, men kommer till en betydande prestandakostnad eftersom CPU-synkronisering är mycket dyr.

Rust har också verktyg för referensräkning och atomreferensräkning, men dessa är opt-in snarare än att vara standard.

värdetyper är däremot stack-allokerade i allmänhet och deras minne hanteras statiskt. Beteendet hos värdetyper i Swift är dock mycket annorlunda än hur Rust hanterar minne. I Swift, värdetyper har vad som kallas ”copy-on-write” beteende kopieras som standard, vilket innebär att varje gång en värdetyp skrivs till en ny variabel, eller skickas till en funktion, en kopia görs.

Copy-on-write kopiering som standard uppnår några av samma mål för lånekontroll: som programmerare behöver du i allmänhet aldrig oroa dig för att ett värde förändras mystiskt på grund av någon oväntad bieffekt någon annanstans i programmet. Det kräver också lite mindre kognitiv belastning än lånekontroll, eftersom det finns hela klasser av ägarrelaterade kompileringsfel i Rust som helt enkelt inte finns i Swift. Men det kommer till en kostnad: dessa ytterligare kopior kräver ytterligare minnesanvändning och CPU-cykler för att slutföra.

i Rust är det också möjligt att kopiera värden som ett sätt att tysta lånekontrollfel, men detta lägger till visuellt brus eftersom kopior måste anges uttryckligen.

så här har vi ett bra exempel på de avvägningar som gjorts av dessa två språk: Swift ger dig några breda antaganden om hur minnet ska hanteras samtidigt som du behåller en säkerhetsnivå. Det är lite som hur en C++ – programmerare kan hantera minne enligt bästa praxis innan man tänker mycket på optimering. Detta gör det väldigt enkelt att hoppa in och skriva kod utan att tänka mycket på detaljer på låg nivå, och även uppnå vissa grundläggande körtidssäkerhet och korrekthet garanterar att du inte skulle få på ett språk som Python eller till och med Golang. Men det kommer med några prestandaklippor, som det är lätt att falla av utan att ens inse det tills du kör ditt program. Det är möjligt att skriva högpresterande Swift-kod, men detta kräver ofta noggrann profilering och optimering för att uppnå.

Rust, å andra sidan, ger dig många specifika verktyg för att specificera hur minnet ska hanteras och lägger sedan några hårda begränsningar för hur du använder dem för att undvika osäkert beteende. Detta ger dig mycket trevliga prestandaegenskaper direkt ur lådan, men det kräver att du tar på dig den extra kognitiva kostnaden för att se till att alla regler följs.

min takeaway från detta har varit att även om dessa språk har några gemensamma mål, de har fundamentalt olika egenskaper som lämpar sig för olika användningsfall. Rust verkar till exempel vara det tydliga valet för något som inbäddad utveckling, där optimal användning av minnes-och CPU-cykler är extremt viktigt, och där kodkompileringsslingan kan vara långsammare, så det är värdefullt att fånga alla möjliga problem vid kompileringstiden. Medan Swift kan vara ett bättre val för något som datavetenskap eller serverlös logik, där prestanda är ett sekundärt problem, och det är värdefullt att arbeta närmare problemdomänen utan att behöva överväga många detaljer på låg nivå.

I alla fall kommer jag att vara mycket intresserad av att följa båda dessa språk i framtiden, och jag kommer att följa det här inlägget med fler observationer om jämförelsen mellan Swift och Rust.

Lämna ett svar

Din e-postadress kommer inte publiceras.