I would like to preface this with a warning: that I don’t really expect any of this to come to direct fruition because both the syntax is terribly unwieldy and because it isn’t really a “problem” and more of difference in programming styles and could just be answered with “well this is the Rust way”.
With that being said here is the problem statement:
Early Returns:
I have generally been both taught and told that it is generally good practice to check errors and then early return as quickly as possible (so as to not do unnecessary work before reporting errors that were known before hand).
This is generally tied with a programming style that has the following sort of format:
{
if var.field1.is_some_err() {
return Err(some_err);
}
if var.field2.is_some_other_err() {
return Err(some_other_err);
}
// Rest of the method follows
...
}
I find that this coding style is easy to both read and understand. However, if we add if let binds to the mix this is no longer possible, because the bindings are bound for the inner scope of the if statement and the error must then be at the bottom of the if in the else statement. This both separates the error returns and indents the code even further.
So I have thought, for some time, if there will ever be a way to “flatten” rust code of if-let statements.
Maybe something like the following (very-bikesheddy)
{
let bind_name;
if !(let Some(@ bind_name) = &fn_call()) {
return Err(some_err);
}
// bind_name is bound to a ref of the inner value returned by fn_call()
...
}
I usually handle it similarly to elidupree – a recent 1.6 kLOC project of mine contains 14 occurrences of the let variable = match expr { …, _ => return } pattern. To me it’s a non-issue; in fact I find this very elegant, given that the combination of several orthogonal features (pattern matching, the language being expression-oriented, and powerful control flow analysis of divergence intermixed with the type system) results in solving a common problem in one or two lines. I really don’t think we need any built-in syntax for this; we should document and teach the pattern instead.
I generally also use this code style. (happy path coding I hear it refered to as). But where ? doesn’t cover it, a match does the job perfectly.
Let bindname = match wierdfn(){
Case::A(v) =>v,
Case::B(e) => return wierderror.into(),
}
//use bindname freely from here
//no crazy nesting and path is still happy.
Let b2 = if let EType(s) = otherfn(bindname){s}else{return};
// Step 1. Accept this pattern as irrefutable:
let A::X(v) = A::X(11);
assert!(v == 11);
// Step 2. Generalize multi-patterns.
// Second pat guarantees irrefutability:
let A::X(v) = input | A::X(22);
// Another example. Third pat diverges:
let A::X(v) = input | alternative | { return };
…I completely forgot about this thread when I posted something about exactly this based on a weekend discussion. See: Idea: Early returns and irrefutability.
Moderators: if this is too similar, just close my new thread and I’ll repost the relevant bits here.