Why must I specify the type for an unused value?

I have rockets code to return JSON pretty or compact based on the query param "pretty". Don't care about the value of "pretty", just needs to be present. i.e request could be api?pretty=1, api?pretty=true or simply api?pretty

The below snippet fails to compile:

impl<'a, T: Serialize> Responder<'a> for Json<T> {
    fn respond_to(self, req: &Request) -> response::Result<'a> {
        let json_stringer = match req.get_query_value("pretty") {
            Some(_) => serde_json::to_string_pretty,
            None => serde_json::to_string,
        };

        json_stringer(&self.0)
            .map(|string| content::Json(string).respond_to(req).unwrap())
            .map_err(|_e| Status::InternalServerError)
    }
}

Compiler error:

32 |         let json_stringer = match req.get_query_value("pretty") {
   |                                       ^^^^^^^^^^^^^^^ cannot infer type for type parameter `T` declared on the associated function `get_query_value`

The type is irrelevant to the handler code. The match clause Some(_) doesn't care about the param's type!

Is this a deliberate limitation of Rust (with good reason) I should learn, or a language bug I should chase up on?
Thanks

This addition of String generic type makes it compile fine, but it feels stupid because the subsequent conditions are irrespective of type and value:

let json_stringer = match req.get_query_value::<String>("pretty") {
            Some(_) => serde_json::to_string_pretty,
            None => serde_json::to_string,
        };

The problem is the compiler still needs to know what type it would have, otherwise it doesn't know which version of get_query_value() to call or how much stack space to set aside for the return value.

Thank you, this helps me understand it better!
However, if we can see that the inner value from Option<T> is never accessed at compile-time or run-time, then (as the rust compiler) we could select the smallest/fastest version of get_query_value() to be called here, I think?

Another reason for the OP is that Rust is statically typed; if it can't figure out the type of a value, that's always an error.


Even if the compiler had some metric for smallest/fastest codes, the methods may have side-effects and other impacts. For example, your method drops a String now. And it's just not good to have the compiler arbitrarily choose something in the face of ambiguity, as it becomes impossible to know what the compiler will do. This would especially hurt if you, the programmer, thought there was only a single possibility, but there were multiple, and the compiler silently picked one you didn't expect.

A logical reason it matters in this case is that which values of the parameter will result in a Some or None varies based on the type. At a guess you may want &RawStr (but I didn't check edge cases; maybe you'll need your own type). If the compiler arbitrarily chose u8 or bool, probably only a small set of values would do what you seem to desire.

3 Likes

But the function you called sure might.

I would expect, for example, that req.get_query_value::<i32>("pretty") would fail if someone passed ?pretty=yes. So even if you aren't consuming it, that doesn't mean the function you're calling doesn't care.

(Also, please pick a different API. ?pretty=no giving pretty output is very confusing.)

1 Like

Thanks @quinedot this extends my knowledge further.
Agreed in my case &RawStr is the logical type param - I don't want the framework to parse a value for the param, just give it to me raw.

I still think my problem is quite ubiquitous and begs a neat solution: rephrased it is

Let me test a function returning Option<T>, has returned Some(_). I am really checking for presence on a thing with "get" methods and unfortunately no "has" methods.

If the language doesn't support that now, could there be an acceptable spec for writing a default function version returning Option<_> - for a caller who doesn't care about the inner value?

Thanks, ?pretty=no would cause a surprising result with this logic.
How would you design it? I guess the simple way is to require ?pretty=1 and reject anything else.

The problem is, whether the function returns a Some(_) or a None could depend on what T is (e.g., if the function calls a trait method on T to decide). So the compiler has no way to resolve an Option<T> method with a generic "Option<_>".

As a side note, it is possible to directly check the existence of a query parameter without parsing it:

impl<'a, T: Serialize> Responder<'a> for Json<T> {
    fn respond_to(self, req: &Request) -> response::Result<'a> {
        let pretty = req.uri().query().map_or(false, |query| {
            FormItems::from(query).any(|item| item.key == "pretty")
        });
        let json_stringer = if pretty {
            serde_json::to_string_pretty
        } else {
            serde_json::to_string
        };

        json_stringer(&self.0)
            .map(|string| content::Json(string).respond_to(req).unwrap())
            .map_err(|_e| Status::InternalServerError)
    }
}
1 Like

That's usually called if blah.get("a").is_some(), which works great on things like a HashMap. But the fact that .parse().is_ok() doesn't work is good because there's no general way to ask "does this parse?" without saying into what you're trying to parse it.

I'll also note that the docs for the method say

This method exists only to be used by manual routing. To retrieve query values from a request, use Rocket’s code generation facilities.

So it's probably fine that it doesn't support what you're trying to do tidily.

And note that it returns Option<Result<T, T::Error>>, so Some(_) is arguably an anti-pattern on it, as it allows ignoring an error without saying what you're ignoring.

The way I generally do APIs (albeit not in Rust) is to accept basically an Option<bool> in those situations. That way:

  • ? and ?pretty and ?pretty= are all "just use the default", so that people can generate query strings conveniently.
  • ?pretty=true and ?pretty=false are supported for setting it
  • ?pretty=1 and ?pretty=if-the-moon-is-full are HTTP 400 Bad Request errors.
2 Likes

Thanks for these points, I accepted your answer.

I'm trying be cleverer than the standard Rockets pattern for parameters; I don't want every request handler function to be aware of ?pretty. The handler will return my JSON struct and then as it gets converted to a Response, we check the request params to decide if the JSON text should be pretty or not. More DRY.

In the end I went with this, I'm not sure if it can be made cleaner...

let json_stringer = match req.get_query_value::<bool>("pretty") {
            Some(val) => match val {
                Ok(true) => serde_json::to_string_pretty,
                _ => serde_json::to_string,
            },
            None => serde_json::to_string,
        };

You can nest patterns, and it will infer the parse type from the context pattern, so that can be just:

let json_stringer = match req.get_query_value("pretty") {
    Some(Ok(true)) => serde_json::to_string_pretty,
    _ => serde_json::to_string,
};

I feel like this is better than you could hope for!

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.