Ok, having gone through and tried to find some examples I've realized where I think this would actually be the most useful. Let's say you working with some api, in the examples below I'll be using the NYC MTA api because it is full of options. Iirc the reason it is mostly options is that they don't have like different messages that are sent. Just a single message and you have to do a bunch of option checking to see if something actually exists. Let's have our main function be something like:
#[tokio::main]
async fn main() -> Result<(), SomeErr> {
let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
let req = Request::builder()
.method("GET")
.uri("https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs")
.header("x-api-key", "api-key")
.body(Body::from(""))?;
// Await the response...
let resp = client.request(req).await?;
let data = hyper::body::to_bytes(resp.into_body()).await;
let feed_message = nyct::FeedMessage::decode(data?);
let feed_entity = feed_message?.entity;
// ...... other stuff ........
Ok(())
}
The structure of a feed_entity is basically options all of the way down but here is a gist with all of their types.
Let's try and grab some of the members of the inside more specifically I want the stop_id
, arrival_time
, and departure_time
of all of the stop_time_updates
printed to the console. If one of these doesn't exist print the default value.
for entity in feed_entity {
if let Some(trip_update) = entity.trip_update {
for stop_time_update in trip_update.stop_time_update {
print!(
"stopId: {} ",
stop_time_update.stop_id.unwrap_or("Default")
);
print!(
"artive time {}",
stop_time_update
.arrival
.and_then(|arrival| arrival.time)
.unwrap_or("Default")
);
print!(
"departure time {}",
stop_time_update
.departure
.and_then(|departure| departure.time)
.unwrap_or("Default")
);
}
}
}
This is starting to get a little unruly because we can't use the ?
on the nested options because the return type is Result
. Yeah we could make another function, but that feels like a work around. Also, this isn't even that deep down, I've seen some GTFS schemas get very complex. Ideally I would want something like this:
for entity in feed_entity {
for stop_time_update in (entity.trip_update?.stop_time_update ?? []) {
print!("stop id: {} ", stop_time_update.stop_id.unwrap_or("Default"));
print!("arrival time: {} ", stop_time_update.arrival?.time ?? "Default");
print!("departure time: {} ", stop_time_update.departure?.stop_id ?? "Default");
}
}
Ok so what's happening here. I've kindof seperated the use case of ??
from unwrap_or
as you can see everything on the LHS of a ??
has a ?
in it. Basically what I want is:
entity.trip_update?.stop_time_update ?? []
to de-sugar to
(|entity: FeedEntity| entity.trip_update?.stop_time_update)(entity)
.unwrap_or([]);
So now we are able to use the ?
on an option to get a nested option without having to make another function because of the closure. Obviously actually typing out this closure is not any better than the original, but with the syntactic sugar it could be really nice. Also, I think ??
would still be able to be used in all of the place unwrap_or
. I think this is the much more important use case that actually makes things a lot easier to read and write.