Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Errori Irreversibili con panic!

A volte si verificano problemi nel codice e non c’è nulla che si possa fare al riguardo. In questi casi, Rust dispone della macro panic!. Esistono praticamente due modi per causare un panic: eseguendo un’azione che causa il panic del codice (come tentare di accedere a un array oltre la fine) o chiamando esplicitamente la macro panic!. In entrambi i casi, causiamo un panic nel nostro programma. Per impostazione predefinita, questi panic stampano un messaggio di errore, eseguono un unwind, puliscono lo stack e terminano. Tramite una variabile d’ambiente, è anche possibile fare in modo che Rust visualizzi lo stack delle chiamate quando si verifica un panic, per facilitare l’individuazione della causa del panic.

Unwinding dello Stack o Interruzione in Risposta a un Panic

Per impostazione predefinita, quando si verifica un panic il programma avvia l’unwinding, il che significa che Rust risale lo stack e pulisce i dati da ogni funzione che incontra. Tuttavia, risalire lo stack e pulire richiede molto lavoro. Rust, quindi, consente di scegliere l’alternativa di abortire immediatamente, che termina il programma senza pulizia. La memoria che il programma stava utilizzando dovrà quindi essere ripulita dal sistema operativo. Se nel progetto è necessario ridurre al minimo il binario risultante, è possibile passare dall’unwinding all’interruzione in caso di panic aggiungendo panic = 'abort' alle sezioni [profile] appropriate nel file Cargo.toml. Ad esempio, se si desidera interrompere l’esecuzione in caso di panic nell’eseguibile finale (release) ma non in fase di sviluppo, aggiungi quanto segue:

[profile.release]
panic = 'abort'

Proviamo a chiamare panic! in un semplice programma:

File: src/main.rs
fn main() {
    panic!("crepa e brucia");
}

Quando si esegue il programma, si vedrà qualcosa di simile a questo:

$ cargo run
   Compiling panic v0.1.0 (file:///progetti/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:2:5:
crepa e brucia
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

La chiamata a panic! causa il messaggio di errore contenuto nelle ultime righe. La prima riga mostra il punto del codice sorgente in cui si è verificato l’errore: src/main.rs:2:5 indica che si tratta della seconda riga, quinto carattere del nostro file src/main.rs.

In questo caso, la riga indicata fa parte del nostro codice e, se andiamo a quella riga, vediamo la chiamata alla macro panic!. In altri casi, la chiamata a panic! potrebbe essere nel codice richiamato dal nostro codice e il nome del file e il numero di riga riportati dal messaggio di errore saranno il codice di qualcun altro in cui viene richiamata la macro panic!, non la riga del nostro codice che alla fine ha portato alla chiamata a panic!.

La seconda riga mostra il nostro messaggio di errore inserito nella chiamata a panic!.

Possiamo usare il backtrace (andare a ritroso) delle funzioni da cui proviene la chiamata panic! per capire la parte del nostro codice che sta causando il problema. Per capire come usare un backtrace di panic!, diamo un’occhiata a un altro esempio e vediamo cosa succede quando una chiamata panic! proviene da una libreria a causa di un bug nel nostro codice invece che dal nostro codice che chiama direttamente la macro. Il Listato 9-1 contiene del codice che tenta di accedere a un indice in un vettore oltre l’intervallo di indici validi.

File: src/main.rs
fn main() {
    let v = vec![1, 2, 3];

    v[99];
}
Listato 9-1: Tentativo di accedere a un elemento oltre la fine di un vettore, che causerà una chiamata a panic!

Qui stiamo tentando di accedere al centesimo elemento del nostro vettore (che si trova all’indice 99 perché l’indicizzazione inizia da zero), ma il vettore ha solo tre elementi. In questa situazione, Rust andrà in panic. L’utilizzo di [] dovrebbe restituire un elemento, ma se si passa un indice non valido, non c’è alcun elemento valido che Rust potrebbe restituire.

In C, tentare di leggere oltre la fine di una struttura dati è un comportamento indefinito. Si potrebbe ottenere qualsiasi cosa si trovi nella posizione in memoria che corrisponderebbe a quell’elemento nella struttura dati, anche se la memoria non appartiene a quella struttura. Questo è chiamato buffer overread (lettura oltre il buffer) e può portare a vulnerabilità di sicurezza se un aggressore riesce a manipolare l’indice in modo tale da leggere dati che non dovrebbe essere autorizzato a leggere e che sono memorizzati dopo la struttura dati.

Per proteggere il programma da questo tipo di vulnerabilità, se si tenta di leggere un elemento in un indice che non esiste, Rust interromperà l’esecuzione e si rifiuterà di continuare. Proviamo e vediamo:

$ cargo run
   Compiling panic v0.1.0 (file:///progetti/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Questo errore punta alla riga 4 del nostro main.rs, dove tentiamo di accedere all’indice 99 del vettore in v.

La riga note: ci dice che possiamo impostare la variabile d’ambiente RUST_BACKTRACE per ottenere un backtrace di ciò che ha causato l’errore. Un backtrace è un elenco di tutte le funzioni che sono state chiamate per arrivare a questo punto. I backtrace in Rust funzionano come in altri linguaggi: la chiave per leggere il backtrace è iniziare dall’inizio e leggere fino a quando non si vedono i file che si sono creati. Quello è il punto in cui si è originato il problema. Le righe sopra quel punto sono il codice che il tuo codice ha chiamato; le righe sottostanti sono il codice che ha chiamato il tuo codice. Queste righe prima e dopo potrebbero includere codice Rust core, codice di libreria standard o pacchetti che stai utilizzando. Proviamo a ottenere un backtrace impostando la variabile d’ambiente RUST_BACKTRACE su un valore qualsiasi tranne 0. Il Listato 9-2 mostra un output simile a quello che vedrai.

$ RUST_BACKTRACE=1 cargo run
thread 'main' (11968) panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
   0: __rustc::rust_begin_unwind
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/std/src/panicking.rs:698:5
   1: core::panicking::panic_fmt
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/core/src/panicking.rs:75:14
   2: core::panicking::panic_bounds_check
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/core/src/panicking.rs:280:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/core/src/slice/index.rs:274:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/core/src/slice/index.rs:18:15
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/alloc/src/vec/mod.rs:3621:9
   6: panic::main
             at ./src/main.rs:4:6
   7: core::ops::function::FnOnce::call_once
             at /rustc/8e62bfd311791bfd9dca886abdfbab07ec54d8b4/library/core/src/ops/function.rs:253:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Listato 9-2: Il backtrace generato da una chiamata a panic! viene visualizzato quando la variabile d’ambiente RUST_BACKTRACE è impostata

Un output enorme! L’output esatto che vedi potrebbe variare a seconda del sistema operativo e della versione di Rust. Per ottenere backtrace con queste informazioni, i simboli di debug devono essere abilitati. I simboli di debug sono abilitati per impostazione predefinita quando si utilizza cargo build o cargo run senza il flag --release, come in questo caso.

Nell’output del Listato 9-2, la riga 6 del backtrace punta alla riga del nostro progetto che causa il problema: la riga 4 di src/main.rs. Se non vogliamo che il nostro programma vada in panic, dovremmo iniziare la nostra analisi dalla posizione indicata dalla prima riga che menziona un file che abbiamo scritto. Nel Listato 9-1, dove abbiamo volutamente scritto codice che andrebbe in panic, il modo per risolvere il problema è non richiedere un elemento oltre l’intervallo degli indici del vettore. Quando in futuro il codice andrà in panic, dovrai capire quale azione sta eseguendo il codice con quali valori tali da causare il panic e cosa dovrebbe fare il codice al suo posto.

Torneremo su panic! e su quando dovremmo e non dovremmo usare panic! per gestire le condizioni di errore nella sezione panic! o non panic! più avanti in questo capitolo. Ora vedremo come gestire un errore utilizzando Result.