如何避免“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");
}
这是一个愚蠢的简化示例,但一般模式是:
- 有一堆不同的函数返回
Option
。 - 所有函数必须按顺序调用,并将前一个函数的返回值(如果不是
None
)传递给下一个函数。 - 如果所有函数都返回
Some
,则返回的最终值将用于某些用途。 - 如果any返回
None
,则执行停止并记录某种错误 - 通常是一条错误消息,准确告知用户哪个函数返回None
。
当然,问题是上面的代码丑陋且不可读。当您用在真实代码中实际含义的变量/函数名称替换 i
、func1
等时,它会变得更加丑陋,并且我的真实代码库中的许多示例都具有更多含义多于四个嵌套的 if let
。这是 箭头反模式 的示例,它完全失败了 squint test,令人困惑的是错误消息如何以与可能导致错误的函数相反的顺序出现。
难道真的没有更好的方法吗?我想将上面的内容重构为具有更清晰、更扁平的结构,其中所有内容都以合理的顺序出现。 if let 链接 可能有帮助,但看起来没有就像 Rust 中已经提供了这个功能一样。我想也许我可以通过使用 ?
和/或提取一些辅助函数来清理东西,但我无法让它工作,而且我不想在整个系统中提取大量新函数如果我能避免的话。
这是我能想到的最好的:
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
等。
有什么我没看到的吗?写我想写的东西最简单、最干净的方法是什么?
I'm wading through a codebase full of code like this:
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");
}
That's a stupid, simplified example, but the general pattern is that:
- There are a bunch of different functions which return an
Option
. - All the functions must be called in sequence, with the return value of the previous function (if it's not
None
) passed to the next function. - If all functions return a
Some
, then the final returned is used for something. - If any returns
None
, then execution stops and some kind of error is logged - usually an error message that informs the user exactly which function returnedNone
.
The problem, of course, is that the above code is an ugly and unreadable. It gets even uglier when you substitute i
, func1
etc. with variable/function names that actually mean something in the real code, and many examples in my real codebase have far more than four nested if let
s. It's an example of the arrow anti-pattern, it completely fails the squint test, and it's confusing how the error messages appear in reverse order to the functions which can cause them.
Is there really not a better way to do this? I want to refactor the above into something that has a cleaner, flatter structure where everything appears in a sensible order. if let chaining might help but it doesn't look like that feature is available in Rust yet. I thought maybe I could clean things up by using ?
and/or extracting some helper functions, but I couldn't get it to work and I'd rather not extract a ton of new functions all over the place if I can avoid it.
Here's the best I could come up with:
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
But this still feels very long and verbose, and I don't like how I'm introducing these extra variables _i
, _j
etc.
Is there something I'm not seeing here? What's the simplest and cleanest way to write what I want to write?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您可以使用 let-else 语句 ,一个功能,已添加到 版本 1.65。
使用此功能,您可以编写:
如果您想在旧版本的 rust 上尝试它,则必须启用不稳定功能:
You can use let-else statements, a feature which was added to stable rust in version 1.65.
With this feature you can write:
If you wanted to try it on an older version of rust, you would have to enable the unstable feature:
If-let 链接将使这变得更好,但现在(假设您不想每晚使用),轻微的重构可能会有所帮助。例如,将除最后一个调用之外的所有调用拉入其自己的函数中,您可以使用
?
运算符:如果您需要对错误情况进行更细粒度的控制,您可以返回一个
结果
改为:If-let chaining will make this a lot nicer, but for now (assuming you don't want to use nightly), it's possible a slight refactor could help. For example, pulling all but the last call of the chain into its own function allows you to use the
?
operator:If you need more fine-grained control over the error cases, you could return a
Result
instead:我想在您的“最终研究”列表中再添加两件事:简单的
Option::and_then
:以及稍微棘手但非常方便的
anyhow::Context :
I'd like to put two more things on your list of "eventually look into" things: The simple
Option::and_then
:And the slightly more tricky, but incredibly convenient
anyhow::Context
:可以使用
if let ... else { return }
的稍微好一点的版本:A slightly better version of the
if let ... else { return }
can be used:我有一个非常相似的问题,@Caesar 的解决方案对我帮助很大。
我想补充一下,在我的例子中,我需要所有涉及的函数的结果,并且我已经修改了该代码以通过这种方式实现这一点:
缺点是它最终可能会产生大量嵌套的
Some 对于长链,但就我而言,我只有两个级别,并且这个解决方案似乎非常有效。
PS:
cargo Clippy
建议使用map
而不是and_then
,在这种情况下确实更短更干净:)I had a very similar issue and the solution of @Caesar helped me a lot.
I would add in my case I need the results of all the functions involved, and I've modified that code to achieve that in this way:
The down side is it could end up by having a lot of nested
Some
for long chains, but in my case I only have two levels and this solution seems to be quite efficient.PS:
cargo clippy
suggested to usemap
instead ofand_then
, which is indeed shorter and cleaner in this case :)当我们等待 if-let-chains 稳定时,还有一个替代方案:
还有
if_chain
板条箱:An alternative while we wait for if-let-chains to be stabilized:
There's also the
if_chain
crate: