In this post, I will discuss one of the my favorite feature of rust – error handling.

Lets start with return statement. One of the key differences between Rust and C is the way they handle return types. In Rust, the return statement is used to explicitly return one or more values from a function. In C, the return statement can be used to return only one value.

Here is an example C program which needs to return 2 values back but since C does not allow multiple return values we are using pointers.


#include <stdio.h>
void multiply_and_divide(float x, float y, float *product, float *quotient) {
    *product = x * y;
    *quotient = x / y;
}
int main() {
    float p, q;
    multiply_and_divide(2, 3, &p, &q);
    printf("result = %f %f\n", p, q);
    return 0;
}

Here is the converted rust program which uses tuple to return multiple values.


fn multiply_and_divide(x: f32, y:f32) -> (f32, f32) {
    let product = x * y;
    let quotient = x / y;
    (product, quotient)
}
fn main() {
    let (p, q) = multiply_and_divide(2f32, 3f32);
    println!("result = {} {}", p, q);
}

Lets take look at another function parse_multiply() which parses a string with format like “x * y” and return the multiplication of x and y. If there are any error during parsing the function would -1 to indicate the error. Using negative value is a standard practice in C, we are using it and to show case the rust’s error handling.


#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int parse_multiply(const char* str) {
    int x = 0;
    int y = 0;

    // find the position of the multiplication sign
    char* pos = strchr(str, '*');
    if (pos == NULL) {
        // if there's no multiplication sign, return an error code
        return -1;
    }

    // convert the substring before the multiplication sign to an integer
    char* endptr;
    x = strtol(str, &endptr, 10);
    if (*endptr != ' ') {
        // if there's no space after the first number, return an error code
        return -1;
    }

    // convert the substring after the multiplication sign to an integer
    y = strtol(pos + 1, &endptr, 10);
    if (*endptr != '\0') {
        // if there's any non-numeric character after the second number, return an error code
        return -1;
    }

    // multiply x and y and return the result
    return x * y;
}
void main() {
    int result1 = parse_multiply("10 * 3");
    int result2 = parse_multiply("10 * 3e");
    printf("result = %d %d\n", result1, result2);
}

The important thing to note about the above program is we are using same value to indicate failure and result. Rust provides better way of returning values and propagating errors by using a `Result` enum.


enum Result {
   Ok(T),
   Err(E),
}

As you can see, this enum can have either return value in `Ok()` if successful or it contains error information in `Err(e)`. Lets convert the above program and use `Result` and see how it looks.


use anyhow::{bail, Result};

fn parse_multiply(s: &str) -> Result<i32>  {
    let split_chunks: Vec<&str> = s.split('*').collect();
    if split_chunks.len() != 2 {
        bail!("Invalid string");
    }
    let x = split_chunks[0].trim();
    let y = split_chunks[1].trim();
    let x: i32 = match x.parse() {
        Ok(v) => v,
        Err(_e) => bail!("Invalid x value ({x})"),
    };
    let y: i32 = match y.parse() {
        Ok(v) => v,
        Err(_e) => bail!("Invalid y value ({y})"),
    };

    // multiply x and y and return the result
    Ok(x * y)
}

fn main() {
    let result1 = parse_multiply("10 * 3").unwrap();
    let result2 = parse_multiply("1 * -1").unwrap();
    let result3 = parse_multiply("10 * 3e").unwrap_or(0);
    println!("result = {} {} {}", result1, result2, result3);
}

There some functions we might not error out and at the same time we don’t want to return values. For example, consider the below grade() function which returns grade for a given score(in string) but returns empty space if score is not available.


#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

char grade(char* str) {
    if (strcmp(str, "NA") == 0) {
        return ' ';
    } else {
        int num = atoi(str);
        if (num < 50) {
            return 'F';
        } else if (num < 70) {
            return 'D';
        } else if (num < 80) {
            return 'C';
        } else if (num < 90) {
            return 'B';
        } else {
            return 'A';
        }
    }
}

int main() {
    printf("Grade: %c\n", grade("19"));
    printf("Grade: %c\n", grade("NA"));
    printf("Grade: %c\n", grade("91"));
    printf("Grade: %c\n", grade("80"));
    return 0;
}

In rust, we can use Option enum to achieve returning value(s) optionally from a function.


pub enum Option {
    None,
    Some(T),
}

The None value is used when a function wants to return nothing and Some is used when the program has something to return.

Here is the rust code which illustrates this:


fn grade(s: &str) -> Option<char>  {
    if s == "NA" {
        return None;
    }
    let num: u32 = s.parse().unwrap();
    let grade_char = if num < 50 {
        'F'
    } else if num < 70 {
        'D'
    } else if num < 80 {
        'C'
    } else if num < 90 {
        'B'
    } else {
        'A'
    };
    Some(grade_char)
}

fn main() {
    println!("Grade: {:?}", grade("19"));
    println!("Grade: {:?}", grade("NA"));
    println!("Grade: {:?}", grade("91"));
    println!("Grade: {:?}", grade("80"));
}

This article can be viewed as video here

Categorized in: