如何避免 "if let" 的嵌套链?

How to avoid nested chains of "if let"?

我正在浏览一个充满这样代码的代码库:

if let Some(i) = func1() {
    if let Some(j) = func2(i) {
        if let Some(k) = func3(j) {
            if let Some(result) = func4(k) {
                // Do something with result
            } else {
                println!("func 4 returned None");
            }
        } else {
            println!("func 3 returned None");
        }
    } else {
        println!("func 2 returned None");
    }
} else {
    println!("func 1 returned None");
}

这是一个愚蠢的简化示例,但一般模式是:

问题当然是上面的代码丑陋且不可读。当您将 ifunc1 等替换为 variable/function 名称时,它会变得更加丑陋,这些名称在实际代码中实际上意味着某些东西,并且我的实际代码库中的许多示例都有超过四个嵌套 if let秒。这是 arrow anti-pattern, it completely fails the squint test 的一个示例,它令人困惑错误消息如何以相反的顺序出现在可能导致它们的函数中。

真的没有更好的方法吗?我想将上面的内容重构为具有更清晰、更扁平结构的内容,其中所有内容都以合理的顺序出现。 if let chaining 可能有帮助,但 Rust 中似乎还没有该功能。我想也许我可以通过使用 ? and/or 提取一些辅助函数来清理一些东西,但我无法让它工作,我不想到处提取大量新函数如果我能避免的话。

这是我能想到的最好的:

let i : u64;
let j : u64;
let k : u64;
let result : u64;

if let Some(_i) = func1() {
    i = _i;
} else {
   println!("func 1 returned None");
   return;
}
if let Some(_j) = func2(i) {
    j = _j;
} else {
   println!("func 2 returned None");
   return;
}
if let Some(_k) = func3(j) {
    k = _k;
} else {
   println!("func 3 returned None");
   return;
}
if let Some(_result) = func3(k) {
    result = _result;
} else {
   println!("func 4 returned None");
   return;
}


// Do something with result

但这仍然感觉很长很冗长,我不喜欢我引入这些额外变量的方式_i_j

这里有什么我没看到的吗?写我想写的东西最简单干净的方法是什么?

有一个unstable feature that will introduce let-else statements

RFC 3137

Introduce a new let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK; construct (informally called a let-else statement), the counterpart of if-let expressions.

If the pattern match from the assigned expression succeeds, its bindings are introduced into the surrounding scope. If it does not succeed, it must diverge (return !, e.g. return or break).

使用此功能,您将能够编写:

let Some(i) = func1() else {
    println!("func 1 returned None");
    return;
};
let Some(j) = func2(i) else {
    println!("func 2 returned None");
    return;
};
let Some(k) = func3(j) else {
    println!("func 3 returned None");
    return;
};
let Some(result) = func3(k) else {
    println!("func 4 returned None");
    return;
};

如果您想立即试用,请使用:

#![feature(let_else)]

If-let 链接会使它变得更好,但是现在(假设您不想每晚使用),可能稍微重构可能会有所帮助。例如,将链的最后一次调用以外的所有调用都拉入其自己的函数中,您可以使用 ? 运算符:

fn get_result() -> Option<u64> {
  let i = func1()?;
  let j = func2(i)?;
  let k = func3(j)?;
  func3(k)
}

fn main() {
  if let Some(result) = get_result() {
    // do something
  }
}

如果您需要更多 fine-grained 控制错误情况,您可以 return 一个 Result 代替:

enum Error {
  Func1,
  Func2,
  Func3,
  Func4,
}

fn get_result() -> Result<i64, Error> {
  let i = func1().ok_or(Error::Func1)?;
  let j = func2(i).ok_or(Error::Func2)?;
  let k = func3(j).ok_or(Error::Func3)?;
  func4(k).ok_or(Error::Func4)
}

fn main() {
  use Error::*;
  match get_result() {
    Ok(result) => {},
    Err(Func1) => {},
    // ...
  }
}

我想在您的“最终调查”事项列表中再添加两件事:简单的 Option::and_then:

let result = func1().and_then(func2).and_then(func3).and_then(func4);
match result {
  Some(result) => …,
  None => …,
}

稍微有点棘手,但非常方便 anyhow::Context:

use anyhow::Context;
let j = func2(i).context("Func2 failed")?;

可以使用 if let ... else { return } 稍微好一点的版本:

let i = if let Some(i) = func1() { i } else {
   println!("func 1 returned None");
   return;
};
let j = if let Some(j) = func2(i) { j } else {
   println!("func 2 returned None");
   return;
};
let k = if let Some(k) = func3(j) { k } else {
   println!("func 3 returned None");
   return;
};
let result = if let Some(result) = func3(k) { result } else {
   println!("func 4 returned None");
   return;
};