Рефакторинг глубоко вложенных Rust `Result`s и` Option`s в более функциональный стиль - PullRequest
0 голосов
/ 09 апреля 2020

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

Идея состоит в том, чтобы открыть структуру данных локальной конфигурации репо git, если это возможно, в противном случае просто вернуть пустую GitConfig. Существует ряд вызовов, которые необходимы для поиска текущего каталога, поиска в нем репо git, открытия репо, а затем открытия внутри него конфигурации, каждый из которых может выдать ошибку или вернуть None. Мне все равно, какая ошибка выдается (или когда возвращается None), за исключением того, что когда выдается ошибка (или возвращается None ), пустой GitConfig возвращается из всей цепочки .

       let local = match env::current_dir() {
            Err(_) => GitConfig::new().unwrap(),
            Ok(cwd) => {
                match find_git_root(&cwd) {
                    None => GitConfig::new().unwrap(),
                    Some(git_path) => {
                        match Repository::open(&git_path) {
                            Err(_) => GitConfig::new().unwrap(),
                            Ok(repo) => {
                                match repo.config() {
                                    Err(_) => GitConfig::new().unwrap(),
                                    Ok(config) => {
                                        config.open_level(Local).unwrap()
                                    }
                                }
                            }
                        }
                    }
                }
            }
        };

Я пытаюсь преобразовать это в более функциональный стиль с использованием комбинаторов, таких как map() and_then() и других, но у меня переполняется стек в голове, пытаясь понять это вне. Я уверен, что есть способ сделать это, принимая во внимание типы возврата Result и Options в цепочке, а также тот факт, что конкретные типы Error в пределах Result отличаются от call to call.

Есть ли хороший, идиоматический c способ написать это функционально, передавая любую ошибку по цепочке, чтобы ее можно было обработать в самом конце с помощью ok_or_else() или map_or_else() или похожие? И как я могу сгладить вложение, чтобы код выглядел чище и было легче читать?

1 Ответ

4 голосов
/ 09 апреля 2020

Существуют ящики типа mdo или map_for, которые позволяют упростить этот код до чего-то вроде:

map_for!{
    cwd <- env::current_dir().ok();
    git_path <- find_git_root (&cwd);
    repo <- Repository::open (&git_path).ok();
    config <- repo.config().ok();
    => config.open_level(Local).unwrap()
}.unwrap_or_else (|| GitConfig::new().unwrap());

Или вы можете сделать это с помощью оператора ? и функции (или замыкания):

(|| {
    let cwd = env::current_dir().ok()?;
    let git_path = find_git_root (&cwd)?;
    let repo = Repository::open (&git_path).ok()?;
    let config = repo.config().ok()?;
    config.open_level(Local).ok()
})().unwrap_or_else (|| GitConfig::new().unwrap());

В конце концов, вы сможете сделать это с try блоком , но они нестабильны на данный момент.

Полное раскрытие: я являюсь автором map_for ящика.

...