Why does this match fail?

Hi everyone,

please consider the following snipped:

fn some_func() -> impl IntoResponse {
    // some code omitted for clarity

    let items = get_data(); // returns a Vec
    
    match items {
        v if v.len() > 0 => results_response(&items),
        _ => empty_response(), // ERROR: `match` arms have incompatible types
    }
}

fn empty_response() -> impl IntoResponse {
    let template = NoResultsTemplate {};
    let html = template.render().unwrap();

    (StatusCode::OK, Html(html).into_response())
}

fn results_response(items: &Vec<Translation>) -> impl IntoResponse {
    let template = ResultsTemplate { 
        results: &items 
    };
    let html = template.render().unwrap();

    (StatusCode::OK, Html(html).into_response())
}

As far as I can see the two functions return the same type thus the match should work. Could someone please point me at what I'm missing?

Thanks very much!

The -> impl IntoResponse is a "Return position impl trait". impl IntoResponse is not a type, it hides a concrete type behind it and exposes only the API provided by the trait IntoResponse. Your two functions do in fact return different concrete types.

4 Likes

It's been a while since I've done http stuff, but does it work if you change your function signatures to this?

fn empty_response() -> impl IntoResponse {
//truncated

    (StatusCode::OK, Html(html)).into_response()
}

fn results_response(items: &Vec<Translation>) -> impl IntoResponse {
//truncated

    (StatusCode::OK, Html(html)).into_response()
}

the difference is subtle, but turning the tuple into a response instead of the html variable may help. I haven't tried compiling this, so it probably won't work.

It won't. impl Trait is distinct from any other impl Trait, even if underlying types are the same.

Ah, okay, I didn't know that returned trait objects abstract return types are always distinct from each other. Makes sense if the point is that the underlying type information should never leak into the calling code. In that case I think this will work:

fn some_func() -> impl IntoResponse {
    // some code omitted for clarity

    let items = get_data(); // returns a Vec
    
    match items {
        v if v.len() > 0 => results_response(&items).into_response(),
        _ => empty_response().into_response(),
    }
}

fn empty_response() -> impl IntoResponse {
    let template = NoResultsTemplate {};
    let html = template.render().unwrap();

    (StatusCode::OK, Html(html))
}

fn results_response(items: &Vec<Translation>) -> impl IntoResponse {
    let template = ResultsTemplate { 
        results: &items 
    };
    let html = template.render().unwrap();

    (StatusCode::OK, Html(html))
}

impl Trait is not a trait object though. If you were using Box<dyn IntoResponse> (I'm not sure if this is possible, can't check right now, but if it is), then these two return types would be the same - dyn Trait is a single specific type of its own.

impl Trait essentially means "there's some type here, but I won't tell you what it is exactly - I just tell you that it's something that implements Trait".

3 Likes

Ah, you're correct. I believe the technical term is abstract return type. Sorry to mix up terminology, accuracy is important!

However, if I now understand what's happening correctly, my last code snippet should address the problem by turning the abstract type from the function calls into the concrete Response type. If I remember correctly, Response also implements the trait into_response as a no op, so the return type of some_func can remain the same.

1 Like