Come molti sviluppatori, sono stato interessato a Rust per un bel po ‘ di tempo. Non solo perché appare in tanti titoli su Hacker News, o per il nuovo approccio che il linguaggio prende per la sicurezza e le prestazioni, ma anche perché le persone sembrano parlarne con un particolare senso di amore e ammirazione. Inoltre, Rust è di particolare interesse per me perché condivide alcuni degli stessi obiettivi e caratteristiche del mio linguaggio preferito: Swift. Dato che di recente ho avuto il tempo di provare Rust in alcuni piccoli progetti personali, ho voluto prendere un po ‘ di tempo per documentare le mie impressioni sulla lingua, specialmente nel modo in cui si confronta con Swift.

The Big Picture

Rust e Swift hanno molte cose in comune: sono entrambi linguaggi compilati con sistemi di tipo potente e moderno e un focus sulla sicurezza. Caratteristiche come i tipi algebrici e la gestione di prima classe dei valori opzionali aiutano a spostare molte classi di errori dal runtime al tempo di compilazione in entrambe queste lingue.

Quindi, come differiscono queste lingue? Il modo migliore per caratterizzare la differenza è:

Swift rende facile scrivere codice sicuro.
Rust rende difficile scrivere codice non sicuro.

Queste due affermazioni potrebbero sembrare equivalenti, ma c’è un’importante distinzione. Entrambe le lingue hanno strumenti per raggiungere la sicurezza, ma fanno diversi compromessi per raggiungerlo: Swift dà la priorità all’ergonomia a scapito delle prestazioni, mentre Rust dà la priorità alle prestazioni a scapito dell’ergonomia.

Il compromesso: Prestazioni vs Ergonomia

Il modo più grande in cui viene dimostrata questa differenza di priorità è nell’approccio che queste lingue hanno alla gestione della memoria. Inizierò con Rust perché l’approccio del linguaggio alla gestione della memoria è uno dei suoi punti di forza unici.

In Rust, la memoria è gestita principalmente staticamente (sì, ci sono altre modalità di gestione della memoria come il conteggio dei riferimenti, ma ignoreremo quelle per ora). Ciò significa che il compilatore Rust analizza il programma e, in base a una serie di regole, decide quando la memoria deve essere allocata e rilasciata.

Per garantire la sicurezza, Rust utilizza una nuova strategia chiamata borrow checking. Il modo in cui funziona in pratica è che, come programmatore, ogni volta che si passa attorno a una variabile (cioè un riferimento a una posizione di memoria), è necessario specificare se il riferimento è mutabile o immutabile. Il compilatore utilizza quindi un insieme di regole per garantire che non sia possibile mutare un singolo pezzo di memoria in due punti contemporaneamente, rendendo così dimostrabile che il programma non ha gare di dati.

Questo approccio ha alcune proprietà molto benefiche rispetto all’utilizzo della memoria e alle prestazioni. Il controllo del prestito può essere molto parsimonioso con la memoria, poiché generalmente evita di copiare i valori. Evita anche il sovraccarico delle prestazioni di una soluzione come la garbage collection, poiché il lavoro viene eseguito in fase di compilazione piuttosto che in fase di runtime.

Tuttavia, presenta alcuni inconvenienti per quanto riguarda la facilità d’uso. A causa della natura della proprietà in Rust, ci sono alcuni modelli di progettazione che semplicemente non funzionano in Rust. Ad esempio, non è banale implementare qualcosa come una lista doppiamente collegata o una variabile globale. Questo probabilmente diventa più intuitivo con il tempo, e ci sono soluzioni alternative per questi problemi, ma Rust impone certamente limitazioni al programmatore che non sono presenti in altre lingue.

Anche se non si parla così spesso di Ruggine, Swift ha anche una storia interessante quando si tratta di gestione della memoria.

Swift ha due tipi fondamentali di variabili: tipi di riferimento e tipi di valore. In generale, i tipi di riferimento sono allocati in heap e sono gestiti dal conteggio dei riferimenti. Ciò significa che in fase di runtime viene tracciato il numero di riferimenti a un oggetto contato riferimento e l’oggetto viene deallocato quando il conteggio raggiunge lo zero. Il conteggio dei riferimenti in Swift è sempre atomico: ciò significa che ogni volta che cambia un conteggio dei riferimenti, deve esserci una sincronizzazione tra tutti i thread della CPU. Ciò ha il vantaggio di eliminare la possibilità che un riferimento venga erroneamente liberato in un’applicazione multi-thread, ma ha un costo significativo in termini di prestazioni poiché la sincronizzazione della CPU è molto costosa.

Rust ha anche strumenti per il conteggio dei riferimenti e il conteggio dei riferimenti atomici, ma questi sono opt-in piuttosto che essere l’impostazione predefinita.

I tipi di valore, al contrario, sono allocati in stack in generale e la loro memoria è gestita staticamente. Tuttavia, il comportamento dei tipi di valore in Swift è molto diverso da come Rust gestisce la memoria. In Swift, i tipi di valore hanno quello che viene chiamato comportamento “copy-on-write” vengono copiati per impostazione predefinita, il che significa che ogni volta che un tipo di valore viene scritto su una nuova variabile o passato a una funzione, viene eseguita una copia.

Copy-on-write La copia di default raggiunge alcuni degli stessi obiettivi del controllo dei prestiti: come programmatore in genere non devi mai preoccuparti di un valore che cambia misteriosamente a causa di qualche effetto collaterale inaspettato altrove nel programma. Richiede anche un po ‘ meno carico cognitivo rispetto al controllo del prestito, poiché ci sono intere classi di errori di compilazione relativi alla proprietà in Rust che semplicemente non esistono in Swift. Tuttavia, ha un costo: quelle copie aggiuntive richiedono un utilizzo della memoria aggiuntivo e cicli di CPU da completare.

In Rust è anche possibile copiare i valori come un modo per silenziare gli errori di controllo del prestito, ma questo aggiunge rumore visivo poiché le copie devono essere specificate esplicitamente.

Quindi qui abbiamo un buon esempio dei compromessi fatti da queste due lingue: Swift ti dà alcune ampie ipotesi su come la memoria dovrebbe essere gestita pur mantenendo un livello di sicurezza. È un po ‘ come un programmatore C++ potrebbe gestire la memoria secondo le migliori pratiche prima di pensare molto all’ottimizzazione. Ciò rende molto facile saltare e scrivere codice senza pensare molto ai dettagli di basso livello, e anche ottenere alcune garanzie di sicurezza e correttezza di base in fase di esecuzione che non si otterrebbe in un linguaggio come Python o anche Golang. Tuttavia viene fornito con alcune scogliere di prestazioni, che è facile cadere senza nemmeno rendersene conto finché non si esegue il programma. È possibile scrivere codice Swift ad alte prestazioni, ma questo spesso richiede un’attenta profilazione e ottimizzazione.

Rust, d’altra parte, offre molti strumenti specifici per specificare come gestire la memoria e quindi pone alcune rigide restrizioni su come utilizzarli per evitare comportamenti non sicuri. Questo ti dà caratteristiche di prestazione molto belle, subito fuori dalla scatola, ma richiede di assumere il sovraccarico cognitivo aggiuntivo di garantire che tutte le regole siano seguite.

Il mio takeaway da questo è stato che mentre queste lingue hanno alcuni obiettivi comuni, hanno caratteristiche fondamentalmente diverse che si prestano a diversi casi d’uso. Rust, ad esempio, sembra la scelta chiara per qualcosa come lo sviluppo embedded, in cui l’uso ottimale dei cicli di memoria e CPU è estremamente importante e in cui il ciclo di compilazione del codice può essere più lento, quindi è utile rilevare ogni possibile problema in fase di compilazione. Mentre Swift potrebbe essere una scelta migliore per qualcosa come la scienza dei dati o la logica serverless, in cui le prestazioni sono una preoccupazione secondaria, ed è prezioso lavorare più vicino al dominio del problema senza dover considerare molti dettagli di basso livello.

In ogni caso, sarò molto interessato a seguire entrambe queste lingue in futuro, e seguirò questo post con ulteriori osservazioni sul confronto tra Swift e Rust.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.