Would someone explain to me why if blocks may not return without matching their else blocks?

Here are two functions. (They also live in this playground). They're both a bit silly. One keeps a mutable value, when it probably doesn't have to, if the second function is allowed. Alas, we must match return values for if blocks. If someone would like to try their hand at a functional solution, I have a gross one here (though this seems to me, obviously more of a recursion type problem).

Silly function that keeps a mutable value:

fn f()-> usize{
    let mut ret = 0;
    for i in 1..1000 {
        for j in i..(1000 - i) {
            let k = 1000 - (i + j);
            if i * i + j * j == k * k {
                ret = i * j * k;
            } 
        }
    }
    ret
}

Rascal function that returns errors for an unmatched if block:

fn f()-> usize{
    for i in 1..1000 {
        for j in i..(1000 - i) {
            let k = 1000 - (i + j);
            if i * i + j * j == k * k {
                i * j * k
            } 
        }
    }
    0
}

The second one isn't returning anything - that would look like

fn f() -> usize {
    for i in 1..1000 {
        for j in i..(1000 - i) {
            let k = 1000 - (i + j);
            if i * i + j * j == k * k {
                return i * j * k;
            } 
        }
    }
    0
}
2 Likes

To clarify, the error in the second one is that an if expression with no else block must return (), and you've written an expression that ultimately evaluates to i32 (but only if the condition is true -- thus this is an incompletely typed program.) It's not actually giving any error about the return value of the function.

The reason is simple: if lone if blocks returned values, Rust code would've potentially be littered with maybe initialized values. Consider this:

let x: i32 = if 2 == a {
    5
}

// use x here. Boom. Code explodes if a isn't 2.

Hope this helps clarify things.

2 Likes

Oh that works? Well that's something to chew on. Why is this different from the thing I think of as just another way to write return?
Eg what is different between the following two lines:
i * j * k
return i * j * k;

If you write i * j * k, as the last statement in a block, that is simply the value assigned to that block. Often the outer block happens to be the function, which results in returning the value. Additionally it is often a block that is itself the last thing in the function, in which case it is also returned.

The return keyword on the other hand will prematurely stop the function at any point and return. The value of return expr is of type the never type ! since execution does not continue after the return expression.

2 Likes

If I'm not mistaken, building on what @L0uisc wrote above, you're saying that the pattern I had misinterpreted as a shorthand return is one that Rust programs use is to assign values to blocks, when the thing I had intended to do was assign a return value to the function.

The former can be used later in the same function, the latter terminates the function. Is that right?

Something like that. Consider this example I just came up with:

let a = if b > 0 {
    i * j * k
} else {
    i + j + k
};

In this case i * j * k is not returned from the function when b is positive, rather the variable a is assigned the value of that expression. Of course had you put return i * j * k inside that if, the function would immediately return had b been positive.

Note that not all blocks can be assigned a value other than (). E.g. an if without an else must return () because the missing else block is implicitly an empty block, and empty blocks return (). Similarly the body of a for loop does not take a value since the value doesn't really have anywhere to go.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.