Salta ai contenuti

Rust vs C++, Go e Python

«Imparo Rust, o Go, o resto con Python?» è una delle domande più frequenti di chi scopre Rust. La risposta onesta è: dipende da cosa vuoi costruire. Ogni linguaggio di questa pagina è un buon linguaggio, usato da milioni di persone. Ognuno ha fatto compromessi diversi.

Pensa ai linguaggi come a dei veicoli. Un’auto sportiva, un furgone e una bicicletta sono tutti «buoni». Solo che non useresti una bicicletta per un trasloco. Questa pagina spiega i compromessi con parole semplici, così puoi scegliere lo strumento giusto per il tuo lavoro. Cercheremo di essere giusti, anche sui punti deboli di Rust.

La promessa principale di Rust è velocità e sicurezza insieme:

  • Veloce come C e C++. Rust compila in codice macchina nativo e non ha un garbage collector, quindi non ci sono pause mentre la memoria viene ripulita.
  • Sicuro per la memoria senza garbage collector. Le regole di ownership (possesso) e di prestito (borrowing) vengono controllate quando compili. Interi gruppi di bug (uso di memoria già liberata, data race tra thread) vengono respinti prima ancora che il programma parta.
  • Ottimi strumenti già pronti. Un solo strumento, Cargo, compila il codice, scarica le librerie, esegue i test e genera la documentazione. Il formattatore (rustfmt) e il linter (clippy) sono ufficiali.
  • Errori del compilatore utili. Spesso il compilatore ti dice esattamente come risolvere il problema.

E agli sviluppatori sembra piacere: nel Stack Overflow Developer Survey 2025 (in inglese), Rust è stato di nuovo il linguaggio «più ammirato», cioè la maggior parte di chi lo usa vuole continuare a usarlo.

Cominciamo con qualcosa di piccolo: sommare i numeri pari di una lista. Ecco la versione in Rust:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let sum: i32 = numbers.iter().filter(|n| *n % 2 == 0).sum();
println!("Somma dei numeri pari: {sum}");
}

Output:

Somma dei numeri pari: 12
▶ Provalo tu

Lo stesso programma in C++:

#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
int sum = 0;
for (int n : numbers) {
if (n % 2 == 0) {
sum += n;
}
}
std::cout << "Somma dei numeri pari: " << sum << "\n";
}

In Go:

package main
import "fmt"
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
sum := 0
for _, n := range numbers {
if n%2 == 0 {
sum += n
}
}
fmt.Println("Somma dei numeri pari:", sum)
}

E in Python:

numbers = [1, 2, 3, 4, 5, 6]
total = sum(n for n in numbers if n % 2 == 0)
print("Somma dei numeri pari:", total)

Tutti e quattro stampano la stessa riga. Python è il più corto, Go e C++ usano un semplice ciclo, e Rust usa una catena di iteratori con una closure (in Rust potresti anche scrivere un ciclo for). Per programmi piccoli come questo, le differenze sono soprattutto di stile. Le differenze vere emergono nei programmi più grandi.

C++ e Rust puntano allo stesso spazio: programmi veloci con pieno controllo sulla memoria, come browser, motori di gioco, database e sistemi operativi. La grande differenza è chi controlla che la memoria sia usata correttamente.

In C++ sei tu. Ecco un bug classico. Un compilatore C++ accetta questo codice senza protestare, ma push_back può spostare il vettore in una nuova zona di memoria, quindi first può puntare a memoria già liberata:

std::vector<int> v = {1, 2, 3};
int& first = v[0];
v.push_back(4); // può spostare la memoria del vettore...
std::cout << first << "\n"; // ...quindi qui si può leggere memoria liberata

Lo stesso errore in Rust non compila:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{first}");
}
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &v[0];
| - immutable borrow occurs here
4 | v.push(4);
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("{first}");
| ----- immutable borrow later used here

Il compilatore (che risponde sempre in inglese) spiega che non puoi prendere v in prestito come mutabile mentre esiste ancora un prestito immutabile, first, che viene usato dopo.

In pratica questo conta moltissimo. Il team di Chromium riporta che circa il 70% dei loro bug di sicurezza gravi sono problemi di sicurezza della memoria (in inglese). Il team Android di Google scrive da diversi anni il codice nuovo in Rust. Nel 2022 ha riportato zero vulnerabilità di sicurezza della memoria trovate nel codice Rust di Android (in inglese), e nel 2025 una densità di vulnerabilità di memoria più di 1000 volte inferiore rispetto al loro codice C e C++ (in inglese). Anche il kernel Linux (in inglese) supporta codice scritto in Rust.

Strumenti: C++ ha molti sistemi di build e gestori di pacchetti, e i team spesso perdono parecchio tempo nella configurazione. Rust ha un solo strumento standard, Cargo, quindi ogni progetto Rust si compila allo stesso modo.

Curva di apprendimento: sono difficili tutti e due. C++ è enorme, e molte sue regole si imparano sbagliando durante l’esecuzione. Le regole di Rust sono severe, ma il compilatore le fa rispettare e le spiega, quindi le impari in fase di compilazione.

Dove C++ vince ancora: decenni di codice esistente, librerie e sviluppatori esperti. Se la tua azienda o il tuo motore di gioco si basa su C++, spesso C++ è la scelta pratica. Rust può chiamare codice C, e C++ tramite interfacce in stile C, ma mescolarli richiede impegno.

Go e Rust vengono spesso confrontati perché sono entrambi moderni, compilati e popolari per server e strumenti da riga di comando. Ma sono stati progettati con obiettivi diversi.

Garbage collection contro ownership. Go usa un garbage collector (in inglese) che trova e libera la memoria inutilizzata mentre il programma gira. Questo rende il codice Go più semplice da scrivere: non pensi mai a chi possiede un valore. Il prezzo è un po’ di memoria e CPU in più, e brevi pause. Rust non ha garbage collector. La memoria viene liberata in un punto noto, quando il proprietario esce dallo scope (ambito). Le prestazioni sono più prevedibili, ma devi imparare ownership e prestito.

Concorrenza. Le goroutine e i canali di Go sono famosi per essere facili. Ecco un piccolo esempio che calcola in parallelo il quadrato di tre numeri e li somma:

results := make(chan int)
for n := 1; n <= 3; n++ {
go func() {
results <- n * n
}()
}
total := 0
for i := 0; i < 3; i++ {
total += <-results
}
fmt.Println("totale:", total)

La versione Rust usa i thread e un canale. È un po’ più prolissa: cloni il mittente per ogni thread e usi move:

use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
for n in 1..=3 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(n * n).unwrap();
});
}
drop(tx); // chiude la nostra copia, così il ciclo qui sotto può finire
let total: i32 = rx.iter().sum();
println!("totale: {total}");
}

Output:

totale: 14
▶ Provalo tu

Cosa ottieni in cambio di quel codice in più: Rust si rifiuta di compilare codice in cui due thread potrebbero modificare gli stessi dati nello stesso momento (una data race). In Go le data race sono possibili; Go offre un race detector (in inglese) che le trova mentre il programma gira. Approfondisci in Concorrenza.

Tempi di compilazione. Go è nato per compilare in fretta. Le sue FAQ dicono che compilare un grande eseguibile dovrebbe richiedere al massimo pochi secondi (in inglese). Rust compila più lentamente, perché il compilatore fa molti più controlli e ottimizzazioni. Te ne accorgerai sui progetti grandi.

Semplicità. Go è volutamente un linguaggio piccolo, con poche funzionalità da imparare, e molte persone trovano di riuscire a leggere il codice Go in fretta. Rust ha più funzionalità (trait, generici, lifetime, macro), che danno più potenza ma richiedono più tempo per essere imparate.

Python e Rust sono molto diversi, e spesso si usano insieme più che uno al posto dell’altro.

Velocità. Python è interpretato, quindi il codice Python puro che fa molti calcoli è molto più lento del codice compilato. Per questo le librerie Python più popolari spesso fanno il lavoro pesante in C, C++ o Rust, dietro le quinte.

Tipi. In Python i tipi vengono controllati mentre il programma gira. Questa funzione funziona con un numero e fallisce con una stringa, ma lo scopri solo quando quella riga viene eseguita:

def add_tax(price):
return price * 1.2
print(add_tax(10)) # 12.0
print(add_tax("10")) # TypeError: can't multiply sequence by non-int of type 'float'

Python ha i type hint opzionali, ma l’interprete Python non li fa rispettare (in inglese). In Rust ogni tipo viene controllato prima che il programma parta:

fn add_tax(price: f64) -> f64 {
price * 1.2
}
fn main() {
println!("{}", add_tax(10.0));
println!("{}", add_tax("10"));
}
error[E0308]: mismatched types
--> src/main.rs:7:28
|
7 | println!("{}", add_tax("10"));
| ------- ^^^^ expected `f64`, found `&str`
| |
| arguments to this function are incorrect

Il compilatore segnala tipi non corrispondenti: la funzione si aspettava un f64 e ha ricevuto un &str.

Quando Python resta lo strumento migliore: script veloci, analisi dei dati, machine learning, notebook, automazione e insegnamento ai principianti. L’ecosistema di Python per la data science è enorme, e puoi provare un’idea in pochi minuti. Se il tuo programma passa la maggior parte del tempo ad aspettare la rete o un database, la velocità di Python spesso non conta.

Usarli insieme: il progetto PyO3 (in inglese) ti permette di scrivere moduli Python in Rust, così puoi tenere il tuo codice Python e spostare in Rust solo la parte lenta. Alcuni strumenti Python molto usati sono scritti proprio in Rust; per esempio, il sondaggio di Stack Overflow nota che il gestore di pacchetti Python uv è scritto in Rust (in inglese).

Sono tendenze generali, non regole rigide. Un programma scritto bene in uno qualsiasi di questi linguaggi può essere «abbastanza veloce».

Rust C++ Go Python
Velocità Molto veloce, codice nativo, niente GC Molto veloce, codice nativo, niente GC Veloce, codice nativo con GC Più lento per calcoli pesanti in Python puro
Sicurezza della memoria Controllata in compilazione (fuori da unsafe) Responsabilità del programmatore GC; data race ancora possibili GC; sicuro, ma gli errori di tipo emergono durante l’esecuzione
Curva di apprendimento Ripida all’inizio Ripida, e il linguaggio è vastissimo Dolce La più dolce
Tempi di compilazione Piuttosto lenti Piuttosto lenti (dipende dal progetto) Molto veloci Nessuna compilazione
Ecosistema Più giovane, in crescita; Cargo incluso Enorme e maturo; tanti strumenti di build Libreria standard solida, ottimo per i server Enorme, soprattutto per dati e ML
Usi tipici Strumenti CLI, server, codice di sistema, WebAssembly, embedded Giochi, browser, sistemi ad alte prestazioni e legacy Servizi web, strumenti cloud e DevOps Scripting, data science, ML, automazione

Fatti queste domande:

  • Ti servono la massima velocità o poco consumo di memoria, per esempio per un motore di gioco, un database o codice su un piccolo dispositivo? Rust (o C++) è una scelta forte.
  • I bug costeranno cari? Il codice sensibile per la sicurezza, i server che girano a lungo e il codice da cui dipendono molte persone sono quelli che guadagnano di più dai controlli di Rust in fase di compilazione.
  • Stai costruendo uno strumento da riga di comando da condividere? Rust produce un unico binario veloce, senza runtime da installare. Anche Go è ottimo qui.
  • Ti serve un servizio web in fretta, con un team nuovo a tutti questi linguaggi? Probabilmente con Go arrivi prima. Anche Rust funziona bene (vedi Progetto: API web), ma il tempo per impararlo è più lungo.
  • È uno script veloce, un’analisi di dati o un esperimento di ML? Usa Python. Passa a Rust più avanti, con PyO3, se una parte diventa troppo lenta.
  • C’è una grande base di codice C++ esistente? Per ora resta su C++, oppure introduci Rust in parti nuove e separate, come ha fatto Android.

E se vuoi semplicemente diventare un programmatore migliore: imparare Rust ti insegna come funziona davvero la memoria, e questa conoscenza ti aiuta in qualsiasi altro linguaggio.

Rust non è perfetto. Ecco le cose che mettono più in difficoltà:

  • La curva di apprendimento c’è davvero. Le prime settimane con il borrow checker possono essere frustranti. Mettiti in conto di «litigare con il compilatore» per un po’. Diventa molto più facile quando l’ownership ti entra in testa.
  • I tempi di compilazione sono più lenti di quelli di Go, soprattutto per progetti grandi e build di release.
  • Alcune cose sono più difficili da esprimere. Dati a forma di grafo, con tanti collegamenti tra oggetti, o codice che condivide molto stato mutabile, richiedono strumenti come gli smart pointer e più pianificazione.
  • L’ecosistema è più giovane. In alcuni ambiti (app con interfaccia grafica, certe librerie scientifiche) ci sono meno opzioni mature che in C++ o Python.
  • Lo usano meno persone (per ora). Nel sondaggio Stack Overflow 2025 (in inglese), molti più partecipanti hanno detto di usare Python (circa il 58%) o C++ (circa il 24%) rispetto a Rust (circa il 15%). Quindi ci sono meno sviluppatori Rust esperti da assumere, a cui chiedere o da cui imparare al lavoro.
  • Più codice per le cose semplici. Come ha mostrato l’esempio dei canali, Rust spesso ti chiede di essere esplicito dove altri linguaggi sono più rilassati.
  • Rust offre la velocità del C++ con la sicurezza della memoria controllata in compilazione, e ha ottimi strumenti integrati.
  • Rispetto a C++, Rust previene intere classi di bug di memoria prima che il programma parta; C++ ha molto più codice e librerie esistenti.
  • Rispetto a Go, Rust non ha garbage collector e blocca le data race in compilazione; Go è più semplice e compila molto più in fretta.
  • Rispetto a Python, Rust è molto più veloce e controlla i tipi prima dell’esecuzione; Python è più rapido da scrivere e domina la data science. PyO3 ti permette di combinarli.
  • I costi principali di Rust sono una curva di apprendimento ripida, compilazioni più lente e un ecosistema più giovane.

Verifica veloce

1. Come gestisce la memoria Rust senza garbage collector?

2. Quale di questi è un vantaggio reale di Go rispetto a Rust?

3. Hai un programma Python in cui una funzione è troppo lenta. Qual è un approccio comune?

4. Nell'esempio C++ contro Rust, cosa succede al codice che tiene &v[0] e poi fa push su v?

Punteggio: 0 / 4