I am parsing this
[xxxxx]
drive0={}
drive1={path="xxxx"}
...
sometimes there is a path, sometimes not.
I have working code but I am still trying to learn the rust idiomatic way of doing things. Code:
for i in 0..8 {
let drive_name = format!("drive{}", i);
if dmap.contains_key(&drive_name) {
if let Some(d) = config[drive_name].as_table() {
this.units.push(Rkunit::new(true));
if d.contains_key("path") {
if let Some(path) = d["path"].as_str() {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.unwrap();
this.units[i].file.replace(file);
}
}
} else {
this.units.push(Rkunit::new(false));
}
}
}
I expected that
if let Some(path) = d["path"].as_str()
(ie without if d.contains()
line)
would deal with both cases - ie no "path" and "path" isnt string, but it does not. Same with the contains_key(drive_name)
too.
I tried various guessed at syntaxes to see if I could avoid another nested if and could find one.
So is there a better way or is this as good as it gets. Any other comments on parsing toml welcome.
There are a few approaches here that might be valid. Since your code is quite complex and uses non-std API's, it's hard to see if my change would be useful:
Use your general code structure, but combined .contains
and applying a function the the contained value into the pattern .get(...).map(...)
. x.get(y)
returns an Option value, which allows you access to the whole Option API, unlike x[y]
which would panic if the key doesn't exist.
if let Some(d) = config.get(&drive_name).map(|c| c.as_table()) {
this.units.push(Rkunit::new(true);
if let Some(path) = d.get("path").and_then(String::as_str) {
}
} else {
this.units.push(Rkunit::new(false));
}
You can use a match statement with some pre-work. I personally prefer this, since it makes the match arms very explicit, but i think it's less idiomatic:
let drive = config.get(&driver_name); // return an option
let path = drive.map(|d|.get("path")); // returns an option
match (drive, path) {
(Some(d), Some(p)) => {
this.units.push(Rkunit::new(true));
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.unwrap();
this.units[i].file.replace(p);
}
(Some(d), None) => {
this.units.push(Rkunit::new(true);
}
_ => {
this.units.push(Rkunit::new(false);
}
}
I think 1. is more idiomatic, but I've certainly seen both and it's probably more a matter of style. Composing Option certainly is idiomatic over contains & access.