Option<T> and Result<T, E> in Depth

with Result

Applying the ? operator to a Result<T, E> inside a function or closure returning Result<U, F> unwraps Ok(v) into v, or converts Err(e) via From::from(e) and returns Err(F) early from the enclosing function.

Desugaring and Automatic From Conversion

let val = fallible_op()?;

// Desugars conceptually to:
let val = match fallible_op() {
    Ok(v) => v,
    Err(e) => return Err(core::convert::From::from(e)),
};

Because the Err(e) branch invokes From::from(e), a single function returning a unified domain error Result<T, ServiceError> can use ? across std::io::Error, std::num::ParseIntError, and std::str::Utf8Error seamlessly as long as ServiceError implements From for each underlying error type:

use std::num::ParseIntError;
use std::str::{self, Utf8Error};

#[derive(Debug)]
enum PacketError {
    InvalidUtf8(Utf8Error),
    InvalidNumber(ParseIntError),
}

impl From<Utf8Error> for PacketError {
    fn from(e: Utf8Error) -> Self {
        PacketError::InvalidUtf8(e)
    }
}

impl From<ParseIntError> for PacketError {
    fn from(e: ParseIntError) -> Self {
        PacketError::InvalidNumber(e)
    }
}

fn decode_ascii_counter(raw_bytes: &[u8]) -> Result<u64, PacketError> {
    let text = str::from_utf8(raw_bytes)?; // Converts Utf8Error -> PacketError
    let number = text.trim().parse::<u64>()?; // Converts ParseIntError -> PacketError
    Ok(number)
}

fn main() {
    println!("Valid bytes:   {:?}", decode_ascii_counter(b" 1024 "));
    println!("Invalid UTF-8: {:?}", decode_ascii_counter(&[0xFF, 0xFE]));
    println!("Invalid int:   {:?}", decode_ascii_counter(b"not_an_int"));
}
Valid bytes:   Ok(1024)
Invalid UTF-8: Err(InvalidUtf8(Utf8Error { valid_up_to: 0, error_len: Some(1) }))
Invalid int:   Err(InvalidNumber(ParseIntError { kind: InvalidDigit }))

Using ? in main() and Unit Tests

Both fn main() and #[test] functions can return Result<(), E> (where E: std::fmt::Debug, such as Result<(), Box<dyn std::error::Error>>), allowing you to use ? directly at the top level of binaries and tests instead of .unwrap():

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let port: u16 = "8080".parse()?;
    println!("Configured port: {port}");
    Ok(())
}
Configured port: 8080