Else Before If In Rust
Tags: cpp, rust, syntax, controlflow
Published: 2020-04-27 03:44:18

Jonathan Boccara has got another new idea in his [Else Before If](https://www.fluentcpp.com/2020/04/24/else-before-if/). I am honestly amazed at all the surprising thoughts he comes up with and how he bends C++ to make them work. He cares about things most don't.

In this case, the idea is about control flow. We have always dealt with edge cases with `if` statements, and it has always been edge cases first, general cases last. Let me quote his example:

```cpp
if (edgeCase1)
{
    // deal with edge case 1
}
else if (edgeCase2)
{
    // deal with edge case 2
}
else
{
    // this is the main case
}
```

I want to note in addition that this is not only C++, but universal. We have always followed the convention that conditions are matched from top to bottom, be it `cond` in Lisp, `match` in ML, `case` in Ruby, etc.

This, despite that we usually come up with the normal case first, and the edge cases after. Despite that we understand the normal case first, and then we can understand the edge cases.

This is a similar problem to error handling. Errors, after all, are exceptional edge cases. For Java and JavaScript programmers, `try ... catch ...` is their very familiar friends.

```java
try {
    // this is the main case
} catch (err) {
    // deal with edge case 1
} catch (edgeCase2) {
    // deal with edge case 1
}
```

Being designed specifically for error handling, `try ... catch ...` has other skills up its sleeves, but syntax-wise it is the same idea.

Jonathan proposes the following syntax:

```cpp
normally
{
    // this is the main case
}
unless (edgeCase1)
{
    // deal with edge case 1
}
unless (edgeCase2)
{
    // deal with edge case 2
}
```

Without modifying the parser, there are some compromises to make. Instead of blocks, each case is instead a closure, and normally is a template function that encloses the unless blocks rather than leading them. I'll leave the end result and the detail of the template magic to [the original post](https://www.fluentcpp.com/2020/04/24/else-before-if/).

Short story, I quickly implemented the idea in Rust, and have since gained appreciation of the separation of macro and generic programming.

Applying Jonathan's strategy, representing the branches as closures, quickly bumps into a large obstacle. The way Jonathan uses the syntax is very C++: declare storage, put in value, display the value at storage:

```cpp
std::string text;
 
normally
{
    text = "normal case";
}
unless (edgeCase1)
{
    text = "edge case 1";
}
unless (edgeCase2)
{
    text = "edge case 2";
}
 
std::cout << textToDisplay << '\n';
```

That means the storage `text` is referred to, and therefore borrowed in, all three branches. In addition, `text` is mutated in each branch. C++ doesn't care much, but Rust has a stand against shared mutation. It is a Rust compiler error for both the normal branch and the unless branches to refer to and mutate `x`.

Why? Because the Rust compiler doesn't know that only one of the branches will be executed. Unfortunately, we cannot tell it either, because Rust neither has the syntax for nor is capable of analyzing general control flow constraints like this. It is not a proof assistant. This is a trade-off among security, freedom of expression, and complexity of management. 

On the other hand, the proposed syntax change is only a reordering of blocks. The change is not semantic, but purely syntactical. It is not some complicated data structure (like BTree) requiring verification for safety. Rust has a better tool for syntactic changes: macro.

```rust
macro_rules! normally {
    ( $norm_br:block $(unless ($cond:expr) $unle_br:block)* ) => {
        if (false) {}
        $(else if $cond $unle_br)*
        else $norm_br
    };
}
```

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=260b80b8ef46c3162bc5de1d1793c952)

You can use the macro like this:

```rust
normally! {
    {
        x = "normal case".to_string();
    }
    unless (n == 10) {
        x = "unless case 1".to_string();
    }
    unless (n == 11) {
        x = "unless case 2".to_string();
    }
}
```

With the `$norm_br:block $(unless ($cond:expr) $unle_br:block)*` pattern, the macro matches `$norm_br` to the first block expression:

```rust
{
    x = "normal case".to_string();
}
```

`$(unless ($cond:expr) $unle_br:block)*` is a pattern group, with the `*` in the end indicating that we are looking for zero to many of it. Within the group, we look for `unless` keyword, after which is a pair of parenthesis enclosing an expression `$cond` representing the edge case, and the block expression to execute for that edge case. 

So the macro sees `unless` and matches `$cond` to `n == 10` and matches `unle_br` to:

```rust
{
    x = "unless case 1".to_string();
}
```

The macro does the same for the second `unless` block, and puts the matches into the example. Notably, `$(else if $cond $unle_br)*` repeats as many times as there are `unless` matches.

In the end the macro turns the `normally!` expression into this:

```rust
if (false) {}
else if n == 10 {
    x = "unless case 1".to_string();
}
else if n == 11 {
    x = "unless case 2".to_string();
}
else {
    x = "normal case".to_string();
}
```

I certainly feel this is simpler than the C++ template magic. C++ template feels like a compromise between a generic type system and a macro system. The mixture of concern really shows in its complexity in my opinion.

---

1. Closure, lambda, they're the same thing.

2. `macro_rules!` is the simpler way to write a macro in Rust, also called macro by example. The harder and more powerful way is procedural macro (`proc_macro`).

3. `unless` is used in Ruby and Lisp to mean "if not", as in "Unless the egg is cooked, don't turn off the stove." It is also natural for "unless" in English to reject the sentence before, as in "Break two eggs, unless you don't have eggs, then pour 200cc of liquid egg." Natural language 🤷‍♂️.

---

If you feel that you've got something from this post, I am glad! Please don't hesitate to comment or reach out.

If you feel that you've got enough that you'd like to donate, please [donate to Wikimedia Foundation](https://donate.wikimedia.org), to which I owe infinitely.