If-let early return on non-match [Pre-Pre-RFC Discussion]

#1

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()
    ...
}
0 Likes

#2

The ? operator can often do this job. When it can’t, there’s always explicit match statements like this:

{
    let bind_name = match &fn_call() {Some(a)=>a, None=>return Err(some_err)};
    ...
}
1 Like

#3
let bind_name = fn_call().ok_or(some_err)?;

Option, Result, and Iterator have approximately infinitely many methods for everything you already need.

1 Like

#4

That only works if fn_call() returns a Result.

0 Likes

#5

Yes I guess this is probably the best bet

0 Likes

#6

ok_or is an Option method that replates the None with the chosen Err.

https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_or

0 Likes

#7

Fair, I just meant that it might be any enum that you are checking. I guess my example could have been better

0 Likes

#8

See also discussion from last year:

1 Like

#9

See also https://github.com/rust-lang/rfcs/issues/2616

0 Likes

#10

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.

1 Like

#11

I think the important line to draw here is:

  • Is this readable?
  • Is this common enough that dedicated syntax would make it more readable?

Survey seems to say “yes, no”.

0 Likes

#12

There are lots of people who say “yes, yes”.

0 Likes

#13

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

I really don’t see a need for this.

1 Like

#14

Here’s a curious concoction.


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

0 Likes

#15

That is a very nice concoction you got there

Sebastian Malton

0 Likes

#16

What does that mean?

It looks like you are doing:

let A::X(v)= ::std::ops::BitOr::bitor(input,A::X(22));

and

let tmp= ::std::ops::BitOr::bitor(input,alternative);
let A::X(v)= ::std::ops::BitOr::bitor(tmp,{return});

For an enum defined like this:

enum A{
    X(SomeType)
}

Here is a playground link of that syntax having pre-existing meaning.

1 Like

#17

Yeah, no luck there… :face_with_raised_eyebrow:

The rhs has to be an expr. I’m mixing up exprs and pats.

0 Likes

#18

…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.

0 Likes