Why doesn't let just return a bool?


#1

So, I was working on a project and came across a situation I had been in before and remain perplexed.

The language has if let and while let explicitly defined, but why not just make the let statement return a bool?

For example, the code I was writing was along these lines:

// items: Vec<Item> further above.
let (modules, other): (Vec<Item>, Vec<Item>) = items.into_iter().partition(|i| {
    if let Mod(_) = i.node {true} else {false}
});

whereas if it is to be believed that if let is to reduce the need for match when you’re looking for a specific case, the following aught to suffice:

let (modules, other): (Vec<Item>, Vec<Item>) = items.into_iter().partition(|i| let Mod(_) = i.node);

Now, I won’t argue that it’s perhaps a bit unreadable, but I don’t think that’s inherent to this method.

It could be argued that this could leave unbound variables in such a case:

let Mod(a) = b;
a.whatever()

These are currently caught by saying error[E0005]: refutable pattern in local binding, however it could just as easily be caught in a similar way to error[E0381]: use of possibly uninitialized variable as occurs when you use let without specifying a value, and don’t set it in every logical path before the error. This could be accomplished by something along the lines of:

let a;
match b {
    Mod(c) => {
        a = c;
        true
    }
    _ => false,
}

I can’t see any reason for it to not be implemented like this, but I would love to hear what people think


#2

There can be many reasons, but to my mind the most convincing reason is that the let “statement” introduces new name(s) to the following statements, not statements inside it (if any). You can make it an expression, but the scope of names defined would be very interesting (if not confusing) when let is in midst of a complex expression.

As a result let is really half statement and half special construct, and in fact it is probably the only “statement” that cannot be used as a sole expression (in contrast to, say, continue or break). This is comparable to ML-like languages where let PAT = EXPR in EXPR is actually a single expression combined—Rust (being a chimera of C++ and ML… I’m kidding!) replaces in with more familiar ; though.

By the way, if you want a pattern-matching expression, the matches crate is probably the best answer so far.


#3

If let just returns bool, then you should be able to do things like if !(let Mod(a) = b) {...}.

So you propose to make let into an expression, but it would either be an expression that cannot be used or nested anywhere except if or while, or it would need to analyze the structure of the expression to figure out which symbols are actually defined. Sounds way more confusing than it currently is.


#4

I think you’re misusing if let in this case. Your initial example might more idiomatically be written as:

let (modules, other): (Vec<Item>, Vec<Item>) = items.into_iter().partition(|i| {
    match i.node {
        Mod(_) => true,
        _ => false,
    }
});

I do think the matches crate mentioned by lifthrasiir looks like a good way to write this in a more compact way.