jgillich
jgillich

Reputation: 76219

How to pass a dynamic amount of typed arguments to a function?

Lets say I want to write a little client for an HTTP API. It has a resource that returns a list of cars:

GET /cars

It also accepts the two optional query parameters color and manufacturer, so I could query specific cars like:

GET /cars?color=black
GET /cars?manufacturer=BMW
GET /cars?color=green&manufacturer=VW

How would I expose these resources properly in Rust? Since Rust doesn't support overloading, defining multiple functions seems to be the usual approach, like:

fn get_cars() -> Cars
fn get_cars_by_color(color: Color) -> Cars
fn get_cars_by_manufacturer(manufacturer: Manufacturer) -> Cars
fn get_cars_by_manufacturer_and_color(manufacturer: Manufacturer, color: Color) -> Cars

But this will obviously not scale when you have more than a few parameters.

Another way would be to use a struct:

struct Parameters {
    color: Option<Color>,
    manufacturer: Option<Manufacturer>
}

fn get_cars(params: Parameters) -> Cars

This has the same scaling issue, every struct field must be set on creation (even if its value is just None).

I guess I could just accept a HashMap<String, String>, but that doesn't sound very good either. So my question is, what is the proper/best way to do this in Rust?

Upvotes: 2

Views: 932

Answers (2)

Matthieu M.
Matthieu M.

Reputation: 299910

I would like to point out that no matter the solution, if you wish for a compile-time checked solution the "url parsing -> compile-time checkable" translation is necessarily hard-wired. You can generate that with an external script, with macros, etc... but in any case for the compiler to check it, it must exist at compile-time. There just is no short-cut.

Therefore, no matter which API you go for, at some point you will have something akin to:

fn parse_url(url: &str) -> Parameters {
    let mut p: Parameters = { None, None };

    if let Some(manufacturer) = extract("manufacturer", url) {
        p.manufacturer = Some(Manufacturer::new(manufacturer));
    }
    if let Some(color) = extract("color", url) {
        p.color = Some(Color::new(color));
    }

    p
}

And although you can try and sugarcoat it, the fundamentals won't change.

Upvotes: 1

isekaijin
isekaijin

Reputation: 19742

You could use the Builder pattern, as mentioned here. For your particular API, it could look like this:

Cars::new_get()
    .by_color("black")
    .by_manufacturer("BMW")
    .exec();

Upvotes: 4

Related Questions