som mange udviklere har jeg været interesseret i Rust i nogen tid. Ikke kun fordi det vises i så mange overskrifter på Hackernyheder, eller på grund af den nye tilgang, sproget tager til sikkerhed og ydeevne, men også fordi folk ser ud til at tale om det med en særlig følelse af kærlighed og beundring. Oven i købet, Rust er af særlig interesse for mig, fordi den deler nogle af de samme mål og funktioner i mit foretrukne go-to-sprog: Hurtig. Da jeg for nylig har taget mig tid til at prøve Rust i nogle små personlige projekter, ville jeg tage lidt tid på at dokumentere mine indtryk af sproget, især i hvordan det sammenlignes med hurtigt.

det store billede

Rust og hurtig har mange ting til fælles: de er begge kompilerede sprog med kraftfulde, moderne typesystemer og fokus på sikkerhed. Funktioner som algebraiske typer og førsteklasses håndtering af valgfrie værdier hjælper med at flytte mange klasser af fejl fra runtime til kompilering af tid på begge disse sprog.

så hvordan adskiller disse sprog sig? Den bedste måde jeg kan karakterisere forskellen på er:

Hurtig gør det nemt at skrive sikker kode.
Rust gør det vanskeligt at skrive usikker kode.

disse to udsagn lyder måske ækvivalente, men der er en vigtig skelnen. Begge sprog har værktøjer til at opnå sikkerhed, men de foretager forskellige afvejninger for at opnå det: hurtig prioriterer ergonomi på bekostning af ydeevne, mens Rust prioriterer ydeevne på bekostning af ergonomi.

afvejningen: Ydeevne vs ergonomi

den største måde, denne forskel i prioritet demonstreres på, er i den tilgang, disse sprog har til hukommelsesstyring. Jeg starter med Rust, fordi sprogets tilgang til hukommelsesstyring er et af dets unikke salgsargumenter.

i Rust styres hukommelsen primært statisk (ja der er andre former for hukommelsesstyring som referencetælling, men vi ignorerer dem for nu). Hvad dette betyder er, at Rust-kompilatoren analyserer dit program og i henhold til et sæt regler beslutter, hvornår hukommelsen skal tildeles og frigives.

for at levere sikkerhed bruger Rust en ny strategi kaldet lånekontrol. Den måde, dette fungerer i praksis på, er, at du som programmør, hver gang du passerer en variabel (dvs.en henvisning til en hukommelsesplacering), skal angive, om referencen er foranderlig eller uforanderlig. Kompilatoren bruger derefter et sæt regler for at sikre, at du ikke kan mutere et enkelt stykke hukommelse to steder på en gang, hvilket gør det beviseligt, at dit program ikke har dataløb.

denne tilgang har nogle meget gavnlige egenskaber med hensyn til hukommelsesforbrug og ydeevne. Lån kontrol kan være meget parsimonious med hukommelse, da det generelt undgår kopiering værdier. Det undgår også ydeevne overhead af en løsning som garbage collection, da arbejdet udføres på kompileringstid snarere end runtime.

det kommer dog med nogle ulemper for så vidt angår brugervenlighed. På grund af ejerskabet i Rust er der nogle designmønstre, som simpelthen ikke virker i Rust. For eksempel er det ikke trivielt at implementere noget som en dobbelt sammenkædet liste eller en global variabel. Dette bliver sandsynligvis mere intuitivt med tiden, og der er løsninger på disse problemer, men Rust pålægger bestemt programmereren begrænsninger, som ikke findes på andre sprog.

selvom det ikke så ofte tales om som Rust, har hurtig også en interessant historie, når det kommer til hukommelsesstyring.

Hurtig har to grundlæggende typer variabler: referencetyper og værdityper. Generelt er referencetyper heap-allokeret, og styres af reference optælling. Dette betyder, at antallet af referencer til et referencetællet objekt spores ved kørsel, og objektet deallokeres, når optællingen når nul. Referencetælling i hurtig er altid atomisk: det betyder, at hver gang en referencetælling ændres, skal der være en synkronisering mellem alle CPU-trådene. Dette har fordelen ved at eliminere muligheden for, at en reference fejlagtigt frigøres i en multi-threaded applikation, men kommer til en betydelig ydelsesomkostning, da CPU-synkronisering er meget dyr.

Rust har også værktøjer til referencetælling og atomreferencetælling, men disse er opt-in snarere end at være standard.

værdityper er derimod stack-allokeret generelt, og deres hukommelse styres statisk. Imidlertid er opførslen af værdityper i hurtig meget forskellig fra, hvordan Rust håndterer hukommelse. I hurtig, værdityper har det, der kaldes “kopi-på-skrive” adfærd kopieres som standard, hvilket betyder, at hver gang en værditype skrives til en ny variabel, eller overføres til en funktion, en kopi er lavet.

kopi-på-Skriv kopiering opnår som standard nogle af de samme mål for lånekontrol: som programmør behøver du generelt aldrig at bekymre dig om en værdi, der ændrer sig mystisk på grund af en uventet bivirkning andetsteds i programmet. Det kræver også lidt mindre kognitiv belastning end lånekontrol, da der er hele klasser af ejerskabsrelaterede kompileringsfejl i Rust, som simpelthen ikke findes i hurtig. Det kommer dog til en pris: disse ekstra kopier kræver yderligere hukommelsesforbrug og CPU-cyklusser for at fuldføre.

i Rust er det også muligt at kopiere værdier som en måde at dæmpe lånekontrolfejl på, men dette tilføjer visuel støj, da kopier skal specificeres eksplicit.

så her har vi et godt eksempel på de afvejninger, der foretages af disse to sprog: Hurtig giver dig nogle brede antagelser om, hvordan hukommelse skal styres, mens du stadig opretholder et sikkerhedsniveau. Det er lidt som hvordan en C++ programmør kan håndtere hukommelse i henhold til bedste praksis, før man tænker meget på optimering. Dette gør det meget nemt at hoppe ind og skrive kode uden at tænke meget på detaljer på lavt niveau, og også opnå nogle grundlæggende kørselssikkerhed og korrekthed garanterer, at du ikke ville få på et sprog som Python eller endda Golang. Men det kommer med nogle præstationsklipper, som det er let at falde af uden selv at indse det, indtil du kører dit program. Det er muligt at skrive hurtig kode med høj ydeevne, men dette kræver ofte omhyggelig profilering og optimering for at opnå.

Rust giver dig på den anden side en masse specifikke værktøjer til at specificere, hvordan hukommelsen skal styres, og placerer derefter nogle hårde begrænsninger for, hvordan du bruger dem til at undgå usikker opførsel. Dette giver dig meget flotte præstationsegenskaber lige ud af kassen, men det kræver, at du påtager dig den ekstra kognitive overhead for at sikre, at alle regler følges.

min afhentning fra dette har været, at mens disse sprog har nogle fælles mål, har de fundamentalt forskellige egenskaber, som egner sig til forskellige brugssager. Rust synes for eksempel det klare valg for noget som indlejret udvikling, hvor optimal brug af hukommelse og CPU-cyklusser er ekstremt vigtigt, og hvor kode-compile-run-sløjfen kan være langsommere, så det er værdifuldt at fange alle mulige problemer på kompileringstidspunktet. Mens hurtig kan være et bedre valg for noget som datalogi eller serverløs logik, hvor ydeevne er en sekundær bekymring, og det er værdifuldt at arbejde tættere på problemdomænet uden at skulle overveje mange af detaljerne på lavt niveau.

under alle omstændigheder vil jeg være meget interesseret i at følge begge disse sprog i fremtiden, og jeg vil følge dette indlæg med flere observationer om sammenligningen mellem hurtig og Rust.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.