идиоматический способ объединить «ключ существует» с «и если это правильный тип» синтаксического анализа toml

я разбираю это

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

иногда есть путь, иногда нет.

У меня есть рабочий код, но я все еще пытаюсь изучить идиоматический способ работы с ржавчиной. Код:

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 приветствуются.


person pm100    schedule 02.09.2020    source источник


Ответы (3)


Здесь есть несколько подходов, которые могут быть верными. Поскольку ваш код довольно сложен и использует нестандартные API, трудно понять, будет ли полезно мое изменение:

  1. Используйте свою общую структуру кода, но объедините .contains и примените функцию к содержащемуся значению в шаблоне .get(...).map(...). x.get(y) возвращает значение Option, которое позволяет вам получить доступ ко всему Option 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. Вы можете использовать оператор match с некоторой предварительной работой. Я лично предпочитаю это, так как это делает руки спички очень явными, но я думаю, что это менее идиоматично:

    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. более идиоматичен, но я определенно видел оба, и, вероятно, это больше вопрос стиля. Вариант создания, безусловно, является идиоматичным по сравнению с содержанием и доступом.

person somnium    schedule 02.09.2020
comment
что вы подразумеваете под нестандартными API? - person pm100; 03.09.2020
comment
@ pm100 нестандартные API: в основном используется нестандартный, поэтому я не могу протестировать его на игровой площадке rust, так как у меня нет доступа к API, который вы используете. - person somnium; 03.09.2020
comment
спасибо за это, v полезно. Я не понял, что могу сделать это причудливое совпадение с кортежем. Но мне проще всего следовать первому - person pm100; 05.09.2020
comment
ну, я попытался включить ваш # 1, и он не компилируется. Я не понимаю, почему бы и нет. rust жалуется, что d.get в третьей строке неправильный. Говорит, что d — это Option‹Map›, чего я не понимаю. поскольку первый Some должен был «не указывать» config.get. (исправил банальную опечатку в строке 2) - person pm100; 07.09.2020
comment
@ pm100 это, вероятно, зависит от того, что возвращает as_table(). В моем примере предполагается, что as_table() возвращает какой-то HashMap-подобный API. например play.rust-lang.org/ работает нормально. Проверьте, что возвращает API любого as_table(), и используйте его соответствующим образом во втором операторе if. - person somnium; 08.09.2020
comment
as_table возвращает Option‹Table› в зависимости от того, является ли элемент таблицей или нет - person pm100; 09.09.2020
comment
я понял - вместо карты нужно and_then - person pm100; 09.09.2020
comment
см. ответ, который я разместил - ты - person pm100; 09.09.2020

Кто-то может счесть цепочку опций более идиоматической, но за ней труднее следовать.

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<()>
person Alexey Larionov    schedule 03.09.2020
comment
@JohnKugelman Здесь нельзя использовать and_then, разница будет заключаться только в необходимости явно возвращать Some(()) - person Alexey Larionov; 03.09.2020
comment
Я бы не назвал это идиоматическим. Функциональные цепочки вызовов не должны иметь побочных эффектов. - person John Kugelman; 03.09.2020
comment
В этом случае соблюдение функциональных идиом приводит к большему раздуванию кода. - person Alexey Larionov; 03.09.2020

основываясь на легком массаже ответа сомния, я закончил с этим. Он кажется более четким, и мне нужно выучить еще немного идиоматической ржавчины.

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

Я знаю, что любые ошибки конфигурации молча игнорируются. Но это то, что я был после. Вероятно, не должно взрываться, если указан недопустимый путь -> Позже

person pm100    schedule 09.09.2020