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

Error handling with Result

We decided to raise an error if the model hasn't been fitted yet using the pattern matching:

#![allow(unused)]
fn main() {
match self.beta {
    Some(beta) => Ok(x * beta),
    None => Err("Model not fitted".to_string()),
}
}

This makes sense because our predict function returns a Result<Array1<f64>, String>. We use the to_string() method to convert the string literal to a regular String as requested. In practice, the user can use this function as follows:

#![allow(unused)]
fn main() {
let y_pred = model.predict(&x).unwrap();
}

which will panic if the model is not fitted, or

let y_pred = model.predict(&x).expect("Model not fitted yet");

to add a custom error message. These methods will make the code crash. Another strategy is to handle the Result with a match too, i.e.,

#![allow(unused)]
fn main() {
match model.predict(&x) {
    Ok(y_pred) => println!("Predicted values: {:?}", y_pred),
    Err(e) => eprintln!("Prediction failed: {}", e),
}
}

In summary, we have two matches that serve different roles:

  • Internal match: is beta available ?
  • External match: did predict work ?

We could also handle other kinds of errors such as dimensionality mismatch. To do, we can implement our own types of errors.

More advanced error handling

We could have gone even further by defining a custom ModelError type as follows.

#![allow(unused)]
fn main() {
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ModelError {
    #[error("Model is not fitted yet")]
    NotFitted,
    #[error("Dimension mismatch")]
    DimensionMismatch,
}
}

This approach uses the thiserror crate to simplify the implementation of the standard Error trait.

By deriving #[derive(Debug, Error)] and annotating each variant with #[error("...")], we define error messages rightaway.

The predict function would be rewritten as:

#![allow(unused)]
fn main() {
pub fn predict(&self, x: &Array1<f64>) -> Result<f64, ModelError> {
    match &self.beta {
        Some(beta) => {
            if beta.len() != x.len() {
                return Err(ModelError::DimensionMismatch);
            }
            Ok(beta.dot(x))
        }
        None => Err(ModelError::NotFitted),
    }
}
}

and could be used as follows:

#![allow(unused)]
fn main() {
match model.predict(&x) {
    Ok(y_pred) => println!("Prediction: {}", y_pred),
    Err(ModelError::NotFitted) => eprintln!("Model is not fitted yet."),
    Err(ModelError::DimensionMismatch) => eprintln!("Input dimension doesn't match."),
}
}