Rust function that can return more than one type?

I'm new to Rust and there is a function in actix-web that intrigue me. To create a router in actix, we need to pass a function (handle_get_insert for example) that implements actix_web::Responder

// main.rs
cfg.service(
    web::resource("/items/get")
        .route(web::get().to(handle_get_item))
        .route(web::head().to(|| HttpResponse::MethodNotAllowed())),

// handle_get_item is a function that returns any type that implement use actix_web::Responder
// Here Responder means any type that it return must have #[derive(Serialize, Deserialize)] 
pub async fn handle_get_item(....) -> impl Responder {
    match reply {
        Ok(item_doc) => {
            let result = GetReply {
                id: String::from(inf.id.to_string()),
                doc: Some(item_doc),
                ok: true,
            };
            // convert the result into json
            return web::Json(result);
        }
        Err(e) => {
            let result = ErrorReply {
                error: format!("{}", e),
                ok: false,
            };
            // error is occurred here: expected struct "GetReply", found struct "ErrorReply"
            return web::Json(result);
        }
    }
}

this handle_get_item can return two type of value (GetReply for data and ErrorReply if an error occurred). Both types have the same traits but with different fields in it.

To my surprise, Rust only recognize one of them as the return type, and throws an error on the ErrorReply:

error: mismatched types
label: expected struct "GetReply", found struct "ErrorReply"

which means that Rust only recognizes GetReply as the returned value and forces each exit point to implements the same GetReply type.

Does it possible to return two types ? or this is not supposed to be happen in actix ?

You can define an enum like this:

enum Either<A, B> {
    Left(A),
    Right(B),
}

Then you can implement Responder from the enum by forwarding any calls to the inner value.

Your framework may already have an enum like this one.

2 Likes

What you probably want is Box<dyn Responder>, not impl Responder.

impl Trait tells the compiler "figure the return type out on yourself". This is useful for hiding details (one famous example is impl Iterator<Item = ...> that allows you to hide the exact iterator combinators you use), and also if you're lazy, or just cannot, name the return type (for example, if it is declared inside the function). But still, the function has a concrete return type. It's just unnamed.

dyn Trait, on the other hand, tells the compiler "I'm going to return something that implements this trait. You will need to use dynamic dispatch to invoke methods on it." dyn Trait is a DST (unsized type), so you have to hide it behind a reference.

For more info read dyn Trait and impl Trait in Rust (ncameron.org).

2 Likes

it turns out that actix-web supports Result<T, E> so I just change the code into:

pub async fn handle_get_item(
    inf: Json<GetInf>,
    app_data: Data<AppState>,
) -> Result<impl Responder, ItemError> {
    let db = app_data.db.clone();
    let reply = Item::from_id(&inf.id, db).await;
    match reply {
        Ok(item_doc) => {
            let result = ReplyFmt {
                id: String::from(inf.id.to_string()),
                data: Some(item_doc),
                ok: true,
            };
            Ok(web::Json(result))
        }
        Err(e) => Err(e),
    }
}
1 Like

If all you do in the error path is to propagate the error, you can use the ? operator:

pub async fn handle_get_item(
    inf: Json<GetInf>,
    app_data: Data<AppState>,
) -> Result<impl Responder, ItemError> {
    let db = app_data.db.clone();
    let item_doc = Item::from_id(&inf.id, db).await?; // if the value returned by the await call
    // is an error, return the error, otherwise map the ok value to `item_doc`
    let result = ReplyFmt {
        id: String::from(inf.id.to_string()),
        data: Some(item_doc),
        ok: true,
    };
    Ok(web::Json(result))
}

Now, to your initial question

fn foo() -> impl Trait means that the function foo is going to return a single type that implements the trait Trait. In your case you where trying to return to different types (ReplyFmt and ItemError). The compiler doesn’t know how to create the glue code required to convert those types into the variant of a compiler-generated enum with two variants. That compiler-generated enum would be a single type, so it would work. But since the compiler doesn’t know how to do it, that’s why you need to manually wrap them in a Either<ReplyFmt, ItemError> enum (or a Result like you finally choose to do).