C ++ ha tre modi per passare i parametri a una funzione: per valore, per riferimento lvalue e per riferimento rvalue. Di questi, il passaggio in base al valore crea la proprietà nel senso che la funzione chiamata riceve la propria copia e il passaggio in base al valore di riferimento indica che il valore può essere consumato, ovvero non verrà più utilizzato dal chiamante. Il passaggio per riferimento lvalore indica che l'oggetto è temporaneamente preso in prestito dal chiamante.
Tuttavia, questi tendono ad essere "per convenzione" e non possono sempre essere controllati dal compilatore. E puoi accidentalmente trasformare un riferimento al valore in un riferimento al valore usandostd::move()
. Concretamente, ci sono tre problemi:
Un riferimento può sopravvivere all'oggetto a cui fa riferimento. Il sistema a vita di Rust lo impedisce.
Possono essere attivi più riferimenti mutabili / non cost in qualsiasi momento. Il correttore di prestito di Rust lo impedisce.
Non è possibile rinunciare ai riferimenti. Non è possibile vedere in un sito di chiamata se tale funzione crea un riferimento al proprio oggetto, senza conoscere la firma della funzione chiamata. Pertanto non è possibile prevenire in modo affidabile riferimenti, né eliminando metodi speciali delle proprie classi né controllando il sito di chiamata per la conformità con alcune guide di stile "senza riferimenti".
Il problema della durata riguarda la sicurezza di base della memoria. È ovviamente illegale utilizzare un riferimento quando l'oggetto di riferimento è scaduto. Ma è molto facile dimenticare la durata in cui si memorizza un riferimento all'interno di un oggetto, in particolare quando quell'oggetto sopravvive all'ambito corrente. Il sistema di tipo C ++ non può spiegarlo perché non modella affatto la durata degli oggetti.
Il std::weak_ptr
puntatore intelligente codifica la semantica della proprietà simile a un semplice riferimento, ma richiede che l'oggetto a cui viene fatto riferimento sia gestito tramite ashared_ptr
, ovvero contato come riferimento. Questa non è un'astrazione a costo zero.
Mentre C ++ ha un sistema const, questo non tiene traccia della possibilità di modificare un oggetto, ma della possibilità di modificare un oggetto attraverso quel particolare riferimento . Ciò non fornisce garanzie sufficienti per una "concorrenza senza paura". Al contrario, Rust garantisce che se esiste un riferimento mutabile attivo che è l'unico riferimento ("Sono l'unico che può cambiare questo oggetto") e se ci sono riferimenti non mutabili, tutti i riferimenti all'oggetto non sono mutabili ("Mentre posso leggere l'oggetto, nessuno può cambiarlo").
In C ++ potresti essere tentato di proteggere l'accesso a un oggetto tramite un puntatore intelligente con un mutex. Ma come discusso sopra una volta che abbiamo un riferimento, può sfuggire alla sua vita prevista. Pertanto un tale puntatore intelligente non può garantire che sia il singolo punto di accesso al suo oggetto gestito. Un tale schema potrebbe effettivamente funzionare nella pratica perché la maggior parte dei programmatori non vuole sabotare se stessa, ma dal punto di vista del sistema di tipi questo è ancora del tutto infondato.
Il problema generale con i puntatori intelligenti è che sono librerie in cima al linguaggio principale. L'insieme delle funzionalità del linguaggio di base abilita questi suggerimenti intelligenti, ad esempio ha std::unique_ptr
bisogno di costruttori di mosse. Ma non possono correggere le carenze nella lingua principale. Le capacità di creare implicitamente riferimenti quando si chiama una funzione e di avere riferimenti penzolanti insieme significano che il linguaggio C ++ di base è insensato. L'incapacità di limitare i riferimenti mutabili a uno solo significa che C ++ non può garantire sicurezza contro le condizioni di gara con qualsiasi tipo di concorrenza.
Naturalmente per molti aspetti C ++ e Rust sono più simili di quanto non siano, in particolare per quanto riguarda i loro concetti di durata dell'oggetto determinata staticamente. Ma mentre è possibile scrivere programmi C ++ corretti (purché nessuno dei programmatori commetta errori), Rust garantisce la correttezza delle proprietà discusse.