zoals veel ontwikkelaars ben ik al geruime tijd geïnteresseerd in Rust. Niet alleen omdat het verschijnt in zoveel krantenkoppen op Hacker News, of vanwege de nieuwe aanpak van de taal neemt om veiligheid en prestaties, maar ook omdat mensen lijken te praten over het met een bepaald gevoel van liefde en bewondering. Op de top van dat, Rust is van bijzonder belang voor mij omdat het deelt een aantal van dezelfde doelen en kenmerken van mijn favoriete go-to taal: Swift. Omdat ik onlangs de tijd heb genomen om Rust uit te proberen in een aantal kleine persoonlijke projecten, wilde ik wat tijd nemen om mijn indrukken van de taal te documenteren, vooral in hoe het zich verhoudt tot Swift.

The Big Picture

Rust en Swift hebben veel gemeen: Ze zijn beide gecompileerde talen met krachtige, moderne typesystemen en een focus op veiligheid. Functies zoals algebraïsche typen en eersteklas afhandeling van optionele waarden helpen om veel foutklassen van runtime naar compilatietijd in beide talen te verplaatsen.

dus hoe verschillen deze talen? De beste manier om het verschil te karakteriseren is:

Swift maakt het gemakkelijk om veilige code te schrijven.
Rust maakt het moeilijk om onveilige code te schrijven.

deze twee verklaringen lijken misschien gelijkwaardig, maar er is een belangrijk onderscheid. Beide talen hebben tools om veiligheid te bereiken, maar ze maken verschillende afwegingen om dat te bereiken: Swift prioriteert ergonomie ten koste van prestaties, terwijl Rust prioriteit geeft aan prestaties ten koste van ergonomie.

de Trade-off: Prestaties versus ergonomie

de grootste manier waarop dit verschil in prioriteit wordt aangetoond, is de benadering van geheugenbeheer in deze talen. Ik begin met Rust omdat de taal ‘ S benadering van geheugenbeheer is een van de unieke selling points.

in Rust wordt het geheugen voornamelijk statisch beheerd (ja, er zijn andere wijzen van geheugenbeheer zoals referentietelling, maar die zullen we voorlopig negeren). Wat dit betekent is, de Rust compiler analyseert uw programma, en volgens een set van regels, bepaalt wanneer geheugen moet worden toegewezen en vrijgegeven.

om de veiligheid te garanderen, gebruikt Rust een nieuwe strategie, genaamd borrowing checking. De manier waarop dit in de praktijk werkt is dat, als een programmeur, elke keer dat je een variabele doorgeeft (dat wil zeggen een verwijzing naar een geheugenlocatie), je moet specificeren of de referentie veranderlijk of onveranderlijk is. De compiler gebruikt dan een set regels om ervoor te zorgen dat je geen enkel stukje geheugen op twee plaatsen tegelijk kunt muteren, waardoor het aantoonbaar is dat je programma geen data races heeft.

deze benadering heeft enkele zeer gunstige eigenschappen met betrekking tot geheugengebruik en prestaties. Lenen controleren kan zeer spaarzaam met het geheugen, omdat het over het algemeen vermijdt kopiëren van waarden. Het vermijdt ook de prestaties overhead van een oplossing zoals garbage collection, omdat het werk wordt gedaan tijdens het compileren in plaats van runtime.

echter, het komt met een aantal nadelen wat betreft het gebruiksgemak. Vanwege de aard van eigendom in roest, zijn er enkele ontwerppatronen die gewoon niet werken in Roest. Bijvoorbeeld, het is niet triviaal om iets als een dubbel gelinkte lijst of een globale variabele te implementeren. Dit wordt waarschijnlijk meer intuïtief met de tijd, en er zijn oplossingen voor deze problemen, maar Rust legt zeker beperkingen op aan de programmeur die niet aanwezig zijn in andere talen.

hoewel er niet zo vaak over wordt gesproken als Rust, heeft Swift ook een interessant verhaal als het gaat om geheugenbeheer.

Swift heeft twee fundamentele typen variabelen: referentietypen en waardetypen. In het algemeen worden referentietypen heap-toegewezen en beheerd door referentietelling. Dit betekent dat tijdens runtime het aantal verwijzingen naar een referentiegeteld object wordt gevolgd en dat het object wordt deallocated wanneer het aantal nul bereikt. Referentietelling in Swift is altijd atomisch: dit betekent dat elke keer dat een referentietelling verandert, er een synchronisatie moet zijn tussen alle CPU threads. Dit heeft het voordeel van het elimineren van de mogelijkheid van een referentie wordt ten onrechte bevrijd in een multi-threaded applicatie, maar komt met een aanzienlijke prestatiekosten als CPU-synchronisatie is erg duur.

Rust heeft ook tools voor referentietelling en atomaire referentietelling, maar deze zijn opt-in in plaats van de standaard.

Waardetypes daarentegen worden in het algemeen stack-toegewezen en hun geheugen wordt statisch beheerd. Echter, het gedrag van waarde types in Swift is veel anders dan hoe Rust omgaat met geheugen. In Swift, waarde types hebben wat wordt genoemd “copy-on-write” gedrag worden standaard gekopieerd, wat betekent dat elke keer dat een waarde type wordt geschreven naar een nieuwe variabele, of doorgegeven aan een functie, een kopie wordt gemaakt.

Copy-on-write kopiëren bereikt standaard een aantal van dezelfde doelen van lenen controleren: als een programmeur hoeft u zich over het algemeen nooit zorgen te maken over een waarde die op mysterieuze wijze verandert als gevolg van een onverwacht neveneffect elders in het programma. Het vereist ook een beetje minder cognitieve belasting dan lenen controle, omdat er hele klassen van eigendom-gerelateerde compilatie-tijd fouten in Rust die gewoon niet bestaan in Swift. Echter, het komt tegen een prijs: die extra kopieën vereisen extra geheugengebruik en CPU cycli te voltooien.

in Rust is het ook mogelijk om waarden te kopiëren als een manier om leesbare controlefouten stil te leggen, maar dit voegt visuele ruis toe omdat kopieën expliciet moeten worden opgegeven.

hier hebben we een goed voorbeeld van de afwegingen gemaakt door deze twee talen: Swift geeft u een aantal brede aannames over hoe geheugen moet worden beheerd met behoud van een niveau van veiligheid. Het is een beetje als hoe een C++ programmeur geheugen zou kunnen omgaan volgens de beste praktijken voordat u veel nadenken over optimalisatie. Dit maakt het heel gemakkelijk om in te springen en code te schrijven zonder veel aandacht te geven aan details op laag niveau, en ook het bereiken van een aantal fundamentele runtime veiligheid en correctheid garanties die je niet zou krijgen in een taal als Python of zelfs Golang. Maar het komt met een aantal prestaties cliffs, die het is gemakkelijk om af te vallen zonder zelfs het realiseren van het totdat u uw programma uit te voeren. Het is mogelijk om high performance Swift code te schrijven, maar dit vereist vaak zorgvuldige profilering en optimalisatie om te bereiken.

Rust, aan de andere kant, geeft u een heleboel specifieke tools voor het specificeren van hoe geheugen moet worden beheerd, en plaatst dan enkele harde beperkingen op hoe u ze gebruikt om onveilig gedrag te voorkomen. Dit geeft u zeer mooie prestatiekenmerken direct uit de doos, maar het vereist dat u op de extra cognitieve overhead van ervoor te zorgen dat alle regels worden gevolgd.

mijn conclusie was dat deze talen weliswaar gemeenschappelijke doelen hebben, maar fundamenteel verschillende kenmerken hebben die zich lenen voor verschillende use-cases. Rust, bijvoorbeeld, lijkt de duidelijke keuze voor iets als embedded ontwikkeling, waar optimaal gebruik van het geheugen en CPU cycli is uiterst belangrijk, en waar de code-compile-run loop kan langzamer zijn, dus het is waardevol om elk mogelijk probleem op te vangen tijdens het compileren. Terwijl Swift misschien een betere keuze is voor iets als data science, of serverless logic, waar prestaties een secundaire zorg zijn, en het waardevol is om dichter bij het probleemdomein te werken zonder veel details op laag niveau te hoeven overwegen.

in ieder geval zal ik zeer geïnteresseerd zijn om beide talen in de toekomst te volgen, en Ik zal dit bericht volgen met meer opmerkingen over de vergelijking tussen Swift en Rust.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.