Indicating error, returning Result of Ok or Err in Rust

Result Error Err Ok return match format! static &'static

Some function we implement might fail. Either because the caller did not supply the correct parameter or some other issue. How can we indicate failure? In some languages failure is indicated by the raising (throwing) of exceptions. In other languages the function can return some value (e.g. null, undef etc.) to indicate failure.

In Rust a function that might need to report an error should return a value of Result type. In general a Result can have either a value wrapped in Ok() or a value wrapped in Err(). What kind of values are wrapped in either of those is up to the author of the function.

In the first example we have a function that expects an integer number between -100 and +100 and then checks if the number is smaller or bigger or exactly the same as the hidden_number. This might be part of a number guessing game where the hidden number was generated by a random generator. However now we would like to focus on the error reporting part.

The guess function return Result<&'static str, &'static str>. The generic description of Result is Result<T, E> so the first type is the type of the successful result the second type is that of the error. In our case they are both static strings that are baked in the binary at compilation time.

In case the user supplied a number that is out of range we return a static string wrapped in the Err() call.

In case the number was in range we supply our message wrapped in an Ok() call.

The caller will receive a value of Result type and it has to be handled somehow. There are plenty of ways to handle it. For example calling unwrap will panic! if the result contained something wrapped in Err and will return the content of the Ok() if the result contained a value wrapped in Ok(). This is mostly used in tests.

The caller can also use match to handle both the Ok() and the Err() case with additional code.

See the functions associated with Result enum to find more ways to handle the errors.

examples/return-result-str/src/main.rs


fn main() {
    let res = guess(42);
    assert_eq!(res, Ok("hit"));
    assert_eq!(res.unwrap(), "hit");

    let res = guess(50);
    assert!(res.is_ok());
    assert_eq!(res, Ok("Too big"));

    let res = guess(0);
    assert!(res.is_ok());
    assert_eq!(res, Ok("Too small"));


    assert!(guess(150).is_err());
    assert_eq!(guess(150), Err("Out of range: too big"));

    assert!(guess(-150).is_err());
    assert_eq!(guess(-150), Err("Out of range: too small"));
}

fn guess(num: i32) -> Result<&'static str, &'static str> {
    let hidden_number = 42;

    if num < -100 {
        return Err("Out of range: too small");
    }
    if num > 100 {
        return Err("Out of range: too big");
    }
    if num < hidden_number {
        return Ok("Too small");
    }
    if num > hidden_number {
        return Ok("Too big");
    }

    Ok("hit")
}

Returning a String generated on the fly

We might want to be more precise in our error message and even include the number we received. In this case we need to generate the error message while the program is already running so we cannot return a static string wrapped in Err(). Therefore we change the second type in the returning Result to be String.

This has some impact on how we generate the error message using the format! macro and how we use String::from when we would like to verify the results.

examples/return-result-string/src/main.rs


fn main() {
    let res = guess(42);
    assert_eq!(res, Ok("hit"));
    assert_eq!(res.unwrap(), "hit");

    let res = guess(50);
    assert!(res.is_ok());
    assert_eq!(res, Ok("Too big"));

    let res = guess(0);
    assert!(res.is_ok());
    assert_eq!(res, Ok("Too small"));


    assert!(guess(150).is_err());
    assert_eq!(guess(150), Err(String::from("Out of range: 150 is too big")));

    assert!(guess(-150).is_err());
    assert_eq!(guess(-150), Err(String::from("Out of range: -150 is too small")));
}

fn guess(num: i32) -> Result<&'static str, String> {
    let hidden_number = 42;

    if num < -100 {
        return Err(format!("Out of range: {num} is too small"));
    }
    if num > 100 {
        return Err(format!("Out of range: {num} is too big"));
    }
    if num < hidden_number {
        return Ok("Too small");
    }
    if num > hidden_number {
        return Ok("Too big");
    }

    Ok("hit")
}

Notes

There are many more types we can return as part of a Result enum.

There are plenty more ways to handle the Result by the caller.

It just occurred to me that including the valid range in the error message would be even more useful. I'll leave it as an exercise to the reader.

Author

Gabor Szabo (szabgab)

Gabor Szabo, the author of the Rust Maven web site maintains several Open source projects in Rust and while he still feels he has tons of new things to learn about Rust he already offers training courses in Rust and still teaches Python, Perl, git, GitHub, GitLab, CI, and testing.

Gabor Szabo