liveBook · Manning

Question mark operator

As explained in Rust Servers, Services, and Apps

The Rust question-mark operator unwraps successful results or propagates errors or absence from the enclosing function.

The Rust `?` operator keeps the successful value of a `Result` or returns its error from the enclosing function. It replaces a common `match` over `Ok` and `Err` without changing which function must handle the failure. section 5.2

Parse before calculating

In `fn square(val: &str) -> Result<i32, ParseIntError>`, `let num = val.parse::<i32>()?;` makes `num` available for squaring when parsing succeeds. For `"INVALID"`, `square` returns the parse error instead. The caller receives `Ok(4)` for `"2"` and an `Err` for invalid input. section 5.2

The enclosing function’s error type matters. Adding file operations to `square` introduces `std::io::Error`, which cannot simply be returned as its declared `ParseIntError`. Use a suitable return error type and convert errors where needed. In the web-service database path, `From<SQLxError> for EzyTutorError` supplies the conversion used by `?`; `map_err` is another way to choose the error before propagating it. section 5.2 section 5.4 section 6.3.3

Listing 5.4 Error handling in the method to retrieve courses for a tutor
pub async fn get_courses_for_tutor_db(
    pool: &PgPool,
    tutor_id: i32,
) -> Result<Vec<Course>, EzyTutorError> {        #1
    // Prepare SQL statement
    let course_rows = sqlx::query!(
        "SELECT tutor_id, course_id, course_name,
        posted_time FROM ezy_course_c5 where tutor_id = $1",
        tutor_id
    )
    .fetch_all(pool)
    .await?;                                     #2
    // Extract result
 
    let courses: Vec<Course> = course_rows
        .iter()
        .map(|course_row| Course {
            course_id: course_row.course_id,
            tutor_id: course_row.tutor_id,
            course_name: course_row.course_name.clone(),
            posted_time: Some(chrono::NaiveDateTime::from(
            course_row.posted_time.unwrap())),
        })
        .collect();
    match courses.len() {                        #3
        0 => Err(EzyTutorError::NotFound(
            "Courses not found for tutor".into(),
        )),
        _ => Ok(courses),
    }
}

Follow `.fetch_all(pool).await?` in the course retrieval function: a successful fetch proceeds to row mapping, while a fetch error returns through its `Result<Vec<Course>, EzyTutorError>` signature instead of panicking at the fetch. section 5.4

A route handler can then decide how to respond. This separates database failure from HTTP response construction: the course handler maps successful results to JSON, while the custom error type supports error responses. The same propagation pattern appears outside handlers when server startup uses `.bind(addr)?` in a `main` function returning `std::io::Result<()>`. section 5.4 section 7.2

How Rust Servers, Services, and Apps covers it

How deep it goes
Works through match replacement, return-error compatibility, conversion, and async database calls without examining operator internals.
Examples
Uses string-to-integer squaring, course and tutor database queries, and web-server binding.
What it assumes
Assumes Rust function and async-handler syntax while defining Result outcomes and explaining why the enclosing function needs a compatible return type.
What you can do after
Apply it via SQLx and Actix Web to propagate database failures through Result-returning functions toward HTTP error responses.
How it connects
Connects Result, unwrap, From, map_err, SQLx, and Actix Web error handling.

Also explained in

Compare how 4 books explain question mark operator →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust Servers, Services, and Apps. The section links point to the parts of the book it draws on.