将 'key exists' 与 'and if its the right type' 解析 toml 结合的惯用方法

idiomatic way to combine 'key exists' with 'and if its the right type' parsing toml

我正在解析这个

[xxxxx]
drive0={}
drive1={path="xxxx"}
...

有时有路径,有时没有。

我有工作代码,但我仍在努力学习 Rust 惯用的做事方式。代码:

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));
        }
    }
}

    

我预计

if let Some(path) = d["path"].as_str()

(即没有 if d.contains() 行)

会处理这两种情况——即没有“路径”和“路径”不是字符串,但事实并非如此。 contains_key(drive_name) 也一样。

我尝试了各种猜测语法,看看是否可以避免另一个嵌套 if 并找到一个。

那么有没有更好的方法,或者说这已经很好了。欢迎对解析 toml 提出任何其他意见。

这里有一些方法可能是有效的。由于您的代码非常复杂并且使用 non-std API,因此很难看出我的更改是否有用:

  1. 使用您的通用代码结构,但组合 .contains 并将包含的值应用到模式 .get(...).map(...) 中。 x.get(y) returns 一个选项值,它允许您访问整个选项 API,不像 x[y] 如果密钥不存在会出现恐慌。

    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));
    }
    
  2. 您可以对某些 pre-work 使用匹配语句。我个人更喜欢这个,因为它使匹配武器非常明确,但我认为它不那么地道:

    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);
        }
    }
    

我认为 1. 更符合惯用语,但我当然都看过,而且可能更像是一种风格问题。组合选项当然是惯用的包含和访问。

有人可能认为选项链更符合惯用语,但更难理解。

config.get(&driver_name)
    .or_else(|| {                            // no drive, passing None all the way down
        this.units.push(Rkunit::new(false));
        None
    })
    .and_then(|drive| {                      // having a drive, trying to get a path
        this.units.push(Rkunit::new(true)); 
        drive.as_table().get("path")
    })
    .map(|path| {                            // only having a path, we're doing the thing
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(path.as_str())             // as_str is there
            .unwrap();
        this.units[i].file.replace(file);
    });
// also "unused Option" warning, because map returns an Option<()>

基于对 somnium 回答的轻微按摩,我以此结束。感觉更清脆,我得学习更多地道的 rust

      for i in 0..8 {
            let drive_name = format!("drive{}", i);

            if let Some(drive) = dmap.get(&drive_name).and_then(|x| x.as_table()) {
                this.units.push(Rkunit::new(true));
                if let Some(path) = drive.get("path").and_then(|x| x.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));
            }
        }

我知道任何配置错误都会被忽略。但这就是我所追求的。如果给出非法路径,可能不应该爆炸 -> 稍后