Un’introduzione alla programmazione asincrona in Rust

I tradizionali modelli di programmazione sincrona spesso portano a colli di bottiglia nelle prestazioni. Questo perché il programma attende il completamento delle operazioni lente prima di passare all’attività successiva. Ciò si traduce spesso in uno scarso utilizzo delle risorse e in un’esperienza utente lenta.

La programmazione asincrona consente di scrivere codice non bloccante che utilizza le risorse di sistema in modo efficace. Sfruttando la programmazione asincrona, puoi progettare app che eseguono più attività. La programmazione asincrona è utile per gestire diverse richieste di rete o elaborare grandi quantità di dati senza bloccare il flusso di esecuzione.

Programmazione asincrona in Rust

Il modello di programmazione asincrona di Rust ti consente di scrivere codice Rust efficiente che viene eseguito contemporaneamente senza bloccare il flusso di esecuzione. La programmazione asincrona è vantaggiosa quando si gestiscono operazioni di I/O, richieste di rete e attività che comportano l’attesa di risorse esterne.

Puoi implementare la programmazione asincrona nelle tue app Rust in diversi modi. Questi includono funzionalità linguistiche, librerie e il runtime Tokio.

Inoltre, il modello di proprietà di Rust e le primitive di concorrenza come canali e blocchi consentono una programmazione concorrente sicura ed efficiente. Puoi sfruttare queste funzionalità con la programmazione asincrona per creare sistemi simultanei in grado di scalare bene e utilizzare più core della CPU.

Concetti di programmazione asincrona di Rust

I future forniscono una base per la programmazione asincrona in Rust. Un futuro rappresenta un calcolo asincrono che non è stato completamente eseguito.

I futures sono pigri (vengono eseguiti solo durante il polling). Quando chiami un metodo future poll(), controlla se il futuro è stato completato o necessita di ulteriore lavoro. Se il futuro non è pronto, restituisce Poll::Pending, indicando che l’attività deve essere pianificata per un’esecuzione successiva. Se il futuro è pronto, restituisce Poll::Ready con il valore risultante.

La toolchain standard di Rust include primitive I/O asincrone, una versione asincrona di file I/O, networking e timer. Queste primitive consentono di eseguire operazioni di I/O in modo asincrono. Ciò consente di evitare di bloccare l’esecuzione di un programma durante l’attesa del completamento delle attività di I/O.

La sintassi async/await consente di scrivere codice asincrono simile al codice sincrono. Ciò rende il codice intuitivo e facile da mantenere.

L’approccio di Rust alla programmazione asincrona enfatizza la sicurezza e le prestazioni. Le regole di proprietà e prestito garantiscono la sicurezza della memoria e impediscono problemi di concorrenza comuni. Async/await syntax e futures forniscono un modo intuitivo per esprimere flussi di lavoro asincroni. È possibile utilizzare un runtime di terze parti per gestire le attività per un’esecuzione efficiente.

Puoi combinare queste funzionalità del linguaggio, librerie e runtime per scrivere codice ad alte prestazioni. Fornisce un framework potente ed ergonomico per la creazione di sistemi asincroni. Ciò rende Rust una scelta popolare per i progetti che richiedono una gestione efficiente delle attività legate all’I/O e un’elevata concorrenza.

Rust versione 1.39 e versioni successive non supportano le operazioni asincrone nella libreria standard di Rust. Avrai bisogno di una cassa di terze parti per utilizzare la sintassi async/await per gestire le operazioni asincrone in Rust. Puoi utilizzare pacchetti di terze parti come Tokio o async-std per lavorare con la sintassi async/await.

Programmazione asincrona con Tokio

Tokio è un robusto runtime asincrono per Rust. Fornisce funzionalità per la creazione di applicazioni altamente performanti e scalabili. Puoi sfruttare la potenza della programmazione asincrona con Tokio. Fornisce inoltre funzionalità per l’estensibilità.

Al centro di Tokio c’è la pianificazione asincrona delle attività e il modello di esecuzione. Tokio ti permette di scrivere codice asincrono con la sintassi async/await. Ciò consente un utilizzo efficiente delle risorse di sistema e l’esecuzione simultanea delle attività. Il ciclo di eventi di Tokio gestisce in modo efficiente la pianificazione delle attività. Ciò garantisce un utilizzo ottimale dei core della CPU e riduce al minimo l’overhead dovuto al cambio di contesto.

I combinatori di Tokio semplificano il coordinamento e la composizione dei compiti. Tokio fornisce potenti strumenti di coordinamento e composizione delle attività. Puoi attendere il completamento di più attività con join, selezionare la prima attività completata con select e gareggiare le attività l’una contro l’altra con race.

Aggiungi la tokio crate alla sezione delle dipendenze del tuo file Cargo.toml.

 [dependencies]
tokio = { version = "1.9", features = ["full"] }

Ecco come puoi usare la sintassi async/await nei tuoi programmi Rust con Tokio:

 use tokio::time::sleep;
use std::time::Duration;

async fn hello_world() {
    println!("Hello, ");
    sleep(Duration::from_secs(1)).await;
    println!("World!");
}

#[tokio::main]
async fn main() {
    hello_world().await;
}

La funzione hello_world è asincrona, quindi può utilizzare la parola chiave await per sospenderne l’esecuzione finché non viene risolto un futuro. La funzione hello_world stampa “Ciao” sulla console. La chiamata alla funzione Duration::from_secs(1) sospende l’esecuzione della funzione per un secondo. La parola chiave await attende il completamento dello sleep future. Infine, la funzione hello_world stampa “World!” alla consolle.

La funzione principale è una funzione asincrona con #[tokio::main] attributo. Designa la funzione principale come punto di ingresso per il runtime di Tokio. hello_world().await esegue la funzione hello_world in modo asincrono.

Ritardare le attività con Tokio

Un’attività prevalente nella programmazione asincrona utilizza ritardi o attività di pianificazione per l’esecuzione in un intervallo di tempo specificato. Il runtime tokio fornisce un meccanismo per l’utilizzo di timer e ritardi asincroni attraverso il modulo tokio::time.

Ecco come puoi ritardare un’operazione con il runtime di Tokio:

 use std::time::Duration;
use tokio::time::sleep;

async fn delayed_operation() {
    println!("Performing delayed operation...");
    sleep(Duration::from_secs(2)).await;
    println!("Delayed operation completed.");
}

#[tokio::main]
async fn main() {
    println!("Starting...");
    delayed_operation().await;
    println!("Finished.");
}

La funzione delay_operation introduce un ritardo di due secondi con il metodo sleep. La funzione delay_operation è asincrona, quindi può utilizzare await per sospenderne l’esecuzione fino al completamento del ritardo.

Gestione degli errori nei programmi asincroni

La gestione degli errori nel codice Rust asincrono comporta l’utilizzo del tipo Result e la gestione degli errori Rust con il ? operatore.

 use tokio::fs::File;
use tokio::io;
use tokio::io::{AsyncReadExt};

async fn read_file_contents() -> io::Result<String> {
    let mut file = File::open("file.txt").await?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;
    Ok(contents)
}

async fn process_file() -> io::Result<()> {
    let contents = read_file_contents().await?;
    
    Ok(())
}

#[tokio::main]
async fn main() {
    match process_file().await {
        Ok(()) => println!("File processed successfully."),
        Err(err) => eprintln!("Error processing file: {}", err),
    }
}

La funzione read_file_contents restituisce un io::Result che rappresenta la possibilità di un errore di I/O. Usando il ? operatore dopo ogni operazione asincrona, il runtime Tokio propagherà gli errori nello stack di chiamate.

La funzione main gestisce il risultato con un’istruzione match che stampa un testo in base al risultato dell’operazione.

Reqwest utilizza la programmazione asincrona per le operazioni HTTP

Molte casse popolari, incluso Reqwest, usano Tokio per fornire operazioni HTTP asincrone.

Puoi utilizzare Tokio con Reqwest per effettuare diverse richieste HTTP senza bloccare altre attività. Tokio può aiutarti a gestire migliaia di connessioni simultanee e gestire in modo efficiente le risorse.