00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Async/Await

La programmazione asincrona in Rust permette di scrivere codice concorrente efficiente senza creare un thread per ogni operazione. Grazie al sistema async/await, le operazioni di I/O possono essere sospese e riprese senza bloccare il thread corrente.

Funzioni Async e .await

Una funzione async restituisce un Future che rappresenta un valore che sarĂ  disponibile in futuro:

async fn saluta(nome: &str) -> String {
    format!("Ciao, {}!", nome)
}

async fn esegui() {
    let messaggio = saluta("Mondo").await;
    println!("{}", messaggio);
}

Il .await sospende l’esecuzione della funzione corrente fino a quando il Future non è pronto. Importante: .await può essere usato solo all’interno di funzioni o blocchi async.

Il Trait Future

Ogni funzione async restituisce un tipo che implementa il trait Future:

use std::future::Future;

// Queste due dichiarazioni sono equivalenti
async fn calcola() -> i32 {
    42
}

fn calcola_equivalente() -> impl Future<Output = i32> {
    async { 42 }
}

Un Future non fa nulla finché non viene “guidato” (polled) da un runtime. Questo è il concetto di lazy evaluation dei Future in Rust.

Blocchi Async

I blocchi async creano Future anonimi inline:

async fn esempio() {
    let futuro = async {
        // operazioni asincrone qui
        let x = 10;
        let y = 20;
        x + y
    };

    let risultato = futuro.await;
    println!("Risultato: {}", risultato);
}

I blocchi async possono catturare variabili dall’ambiente circostante, come le closures:

async fn esempio_cattura() {
    let nome = String::from("Rust");

    let futuro = async move {
        println!("Linguaggio: {}", nome);
    };

    futuro.await;
}

Runtime: Tokio

Rust non include un runtime asincrono nella libreria standard. Il runtime piu utilizzato è Tokio. Per usarlo, aggiungi nel Cargo.toml:

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

Ecco un esempio completo con Tokio:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    println!("Inizio");

    let task1 = async {
        sleep(Duration::from_secs(2)).await;
        println!("Task 1 completato");
    };

    let task2 = async {
        sleep(Duration::from_secs(1)).await;
        println!("Task 2 completato");
    };

    // Esegue entrambi i task concorrentemente
    tokio::join!(task1, task2);

    println!("Fine");
}

Spawning di Task Asincroni

tokio::spawn crea un nuovo task asincrono che viene eseguito in modo concorrente:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        sleep(Duration::from_millis(500)).await;
        "Risultato dal task"
    });

    // Altre operazioni mentre il task è in esecuzione
    println!("Task lanciato, attendo...");

    let risultato = handle.await.unwrap();
    println!("{}", risultato);
}

Combinare Future con join! e select!

join! esegue piĂą Future concorrentemente e attende che tutti completino:

use tokio::time::{sleep, Duration};

async fn scarica_pagina(url: &str) -> String {
    sleep(Duration::from_millis(100)).await;
    format!("Contenuto di {}", url)
}

#[tokio::main]
async fn main() {
    let (pag1, pag2, pag3) = tokio::join!(
        scarica_pagina("https://esempio.it/1"),
        scarica_pagina("https://esempio.it/2"),
        scarica_pagina("https://esempio.it/3"),
    );

    println!("{}\n{}\n{}", pag1, pag2, pag3);
}

select! attende il primo Future che completa e cancella gli altri:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    tokio::select! {
        _ = sleep(Duration::from_secs(1)) => {
            println!("Timer scaduto");
        }
        _ = async {
            sleep(Duration::from_millis(500)).await;
        } => {
            println!("Operazione completata per prima");
        }
    }
}

Canali Asincroni

Tokio fornisce canali asincroni simili a quelli della libreria standard:

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32); // buffer di 32 messaggi

    let tx2 = tx.clone();

    tokio::spawn(async move {
        tx.send("Ciao dal task 1").await.unwrap();
    });

    tokio::spawn(async move {
        tx2.send("Ciao dal task 2").await.unwrap();
    });

    while let Some(msg) = rx.recv().await {
        println!("Ricevuto: {}", msg);
    }
}

Tokio offre anche oneshot per comunicazioni singole e broadcast per multi-consumer:

use tokio::sync::oneshot;

#[tokio::main]
async fn main() {
    let (tx, rx) = oneshot::channel();

    tokio::spawn(async move {
        tx.send(42).unwrap();
    });

    let valore = rx.await.unwrap();
    println!("Ricevuto: {}", valore);
}

Pinning

Alcuni Future devono essere “pinned” in memoria perché contengono auto-referenze. Pin garantisce che il dato non venga spostato:

use std::pin::Pin;
use std::future::Future;

async fn operazione() -> i32 {
    42
}

fn richiede_pin(futuro: Pin<&mut dyn Future<Output = i32>>) {
    // ...
}

async fn esempio_pin() {
    let mut fut = Box::pin(operazione());
    // fut è ora Pin<Box<dyn Future<Output = i32>>>
    let risultato = fut.as_mut().await;
    println!("{}", risultato);
}

In pratica, Box::pin() è il modo piu semplice per creare un Future pinned.

Runtime async-std

Un’alternativa a Tokio è async-std, che offre un’API simile alla libreria standard:

[dependencies]
async-std = "1"
use async_std::task;
use std::time::Duration;

fn main() {
    task::block_on(async {
        let handle = task::spawn(async {
            task::sleep(Duration::from_millis(100)).await;
            "Completato"
        });

        let risultato = handle.await;
        println!("{}", risultato);
    });
}

Conclusione

La programmazione asincrona in Rust offre concorrenza efficiente senza il costo dei thread del sistema operativo. Le funzioni async e l’operatore .await rendono il codice asincrono leggibile quasi quanto quello sincrono. Ricorda che i Future in Rust sono lazy e necessitano di un runtime (come Tokio o async-std) per essere eseguiti. Usa join! per attendere piu Future insieme, select! per reagire al primo che completa, e i canali asincroni per la comunicazione tra task.