La gestione degli errori in Rust

La gestione degli errori in Rust

La gestione degli errori è un aspetto fondamentale quando si crea un programma o una funzione. Può succedere qualsiasi cosa di inaspettato: un file che non esiste o un puntamento di memoria dove non c’è nessun valore.

  • In genere dobbiamo prevedere cosa potrebbe succedere e dire al programma cosa fare.

Alcuni tipi di errore non sono gravi mentre altri possono compromettere il funzionamento del programma.

Panic

  • Quando il programma trova un errore e non ha alternative richiamerà il macro panic!(). Mostrerà nella console un messaggio di errore e ci indicherà il punto del codice dove è successo. Ci darà anche una lista di azioni compiute prima e dopo la chiamata alla funzione principale.
  • Possiamo anche chiamare noi la macro e indicare un messaggio da mostrare. Possiamo anche usare il metodo expect()
fn main() {
    panic!("This is a panic");
}

Console:
thread 'main' panicked at examples/panic.rs:2:5:
This is a panic
stack backtrace:
   0: rust_begin_unwind
             at /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/std/src/panicking.rs:665:5
   1: core::panicking::panic_fmt
             at /rustc/90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf/library/core/src/panicking.rs:74:14
   2: panic::main
             at ./examples/panic.rs:2:5
   3: core::ops::function::FnOnce::call_once
             at /home/marco/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
  • Come impostazione predefinita, quando il programma trova un errore e va in panico, pulisce tutto i dati conservati nello stack gestiti dal programma.
  • Tuttavia, potremmo volere avere il programma più leggero possibile dopo la compilazione e rimuovere le istruzioni di pulizia. Per farlo dobbiamo inserire la giusta informazione su Cargo.toml.
[package]
name = "rust-tutorial"
version = "0.1.0"
edition = "2021"

[dependencies]

[profile.release]
panic = 'abort'

Result

  • Possiamo tornare l’enum Result per gestire gli errori. Questo enumeratore ha due valori: Ok() e Err().
  • Funziona in modo simile a Option<> ma è più indicato per gestire gli errori e permette anche di scrivere codice breve e conciso.
  • Possiamo inserire al suo interno sia errori reversibili che irreversibili.
use std::{fs::File, io::{self, Read}};

fn main() {
    let content = read_content_from_file("hello.txt").unwrap(); //Ottieni panic se c'è un errore

    println!("{}",content)
}

fn read_content_from_file(path: &str) -> Result<String, io::Error> {
    let mut file_result = File::open(path)?;

    let mut file = match file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut content = String::new();

    match file.read_to_string(&mut content) {
        Ok(_) => Ok(content),
        Err(e) => Err(e),
    }
}
use std::{fs::File, io::{self, Read}};

fn main() {
    let content = read_content_from_file("hello.txt").unwrap_or_default(); //Il programma continua senza panic

    println!("{}",content)
}

fn read_content_from_file(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut content = String::new();

    //Reads all bytes until EOF in this source, appending them to buf.
    file.read_to_string(&mut content)?;
    Ok(content)
}
use std::{fs::File, io::{self, Read}};

fn main() {
    let content = read_content_from_file("hello.txt").unwrap_or_default();

    println!("{}",content)
}

fn read_content_from_file(path: &str) -> Result<String, io::Error> {
    let mut content = String::new();
    //Reads all bytes until EOF in this source, appending them to buf.
    File::open(path)?.read_to_string(&mut content)?;
    Ok(content)
}
use std::{fs, io};

fn main() {
    let content = read_content_from_file("hello.txt").unwrap_or_default();

    println!("{}",content)
}

fn read_content_from_file(path: &str) -> Result<String, io::Error>  {
    //Reads the entire contents of a file into a string.
    //
    // This is a convenience function for using File::open and [read_to_string] 
    // with fewer imports and without an intermediate variable.
    fs::read_to_string(path)
}

Codice finale:

use std::{fs, io};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = read_content_from_file("hello.txt")?;

    Ok(println!("{}",content))
}

fn read_content_from_file(path: &str) -> Result<String, io::Error>  {
    //Reads the entire contents of a file into a string.
    //
    // This is a convenience function for using File::open and [read_to_string] 
    // with fewer imports and without an intermediate variable.
    fs::read_to_string(path)
}

Riassumendo:

  • In caso di operazioni che possono fallire o per segnalare errori di inupt e output ritorniamo Result<String, io::Error>
  • In caso in cui il risultato è facoltativo e non è richiesto un errore (es. formule matematiche che possono ritornare None), possiamo ritornare un Option<T>.
  • Per le funzioni principali che gestiscono vari tipi di errori ritorniamo Result<(), Box <dyn Error>>