Here is some code I wrote to scan the header of a CSV file and determine whether all the columns I care about are available.
struct LandmarkFileColumns {
addr: usize,
longitude: usize,
latitude: usize,
distance: usize,
}
#[derive(Debug, Fail)]
#[fail(display="missing columns: {}", _0)]
struct LandmarkFileMissingColumnsError(String);
fn select_columns(header: &csv::StringRecord, location: &str)
-> Result<LandmarkFileColumns, LandmarkFileMissingColumnsError> {
let mut addr: Option<usize> = None;
let mut longitude: Option<usize> = None;
let mut latitude: Option<usize> = None;
let mut distance: Option<usize> = None;
let mut wanted = 4;
for (index, field) in header.iter().enumerate() {
match field {
"addr" => { addr = Some(index); wanted -= 1; },
"longitude" => { longitude = Some(index); wanted -= 1; },
"latitude" => { latitude = Some(index); wanted -= 1; },
f if f == location => { distance = Some(index); wanted -= 1; },
_ => {},
}
if wanted == 0 { break }
}
if wanted == 0 {
Ok(LandmarkFileColumns {
addr: addr.unwrap(),
longitude: longitude.unwrap(),
latitude: latitude.unwrap(),
distance: distance.unwrap(),
})
} else {
let mut missing: Vec<&str> = Vec::with_capacity(4);
if addr.is_none() { missing.push("addr"); }
if longitude.is_none() { missing.push("longitude"); }
if latitude.is_none() { missing.push("latitude"); }
if distance.is_none() { missing.push(location); }
Err(LandmarkFileMissingColumnsError(missing.join(", ")))
}
}
This works but it feels redundant and not as provably correct as it should be. Is there a tighter way to write it? Particularly, is there a better way to express "if all four of these Options are Some then [break out of the loop / unwrap all of them]" ? "Better" means "easier for human readers and/or the compiler to see that this function cannot panic."