2

I'm new to rust (coming from programming in c/c++ and python) so to learn I'm writing some basic functions. Below I have a factorial function that takes in a signed integer and has two if checks for it.

fn factorial(x: i32) -> i32 {
    let result = if x > 1 {
        x * factorial(x-1)
    } else if x <= 1 {
        1
    };

    result
}

To my knowledge, the if and else-if blocks should handle every case for it. However, when compiling it throws the following error:

error[E0317]: `if` may be missing an `else` clause
  --> src/main.rs:22:12
   |
22 |       } else if x <= 1 {
   |  ____________^
23 | |         1
   | |         - found here
24 | |     };
   | |_____^ expected `()`, found integer
   |
   = note: `if` expressions without `else` evaluate to `()`
   = help: consider adding an `else` block that evaluates to the expected type

error: aborting due to previous error

For more information about this error, try `rustc --explain E0317`.
error: could not compile `functions`

If I replace the else-if with just an else, it compiles just fine. Why do I need to replace it with an else? Shouldn't the previous else-if be good enough?

chaotic
  • 21
  • 3
  • 3
    Upvoted, I think this is an interesting question about how smart the compiler is, but in the real world I would tell you that writing an `else if` that's the exact inverse of the `if` is extraneous and to just write the `else` :-) – John Ledbetter Mar 28 '21 at 02:31
  • Thanks for the response! Just to make sure I'm understanding you properly then, it just comes down to the coding standard really? – chaotic Mar 28 '21 at 02:33
  • 1
    The semantics of rust are such that an `if` without an `else` must have type `()`. There are a couple reasons for this---for one, your definite assignment analysis doesn't need to try and determine if a branch is unreachable (that sort of property would be undecidable in the general case). – kopecs Mar 28 '21 at 02:37
  • Does this answer your question? [Why does an if without an else always result in () as the value?](https://stackoverflow.com/questions/43335193/why-does-an-if-without-an-else-always-result-in-as-the-value) – kopecs Mar 28 '21 at 02:37
  • @kopecs I saw that thread, but the solution mentioned how there would be cases where the result obtains something that is not handled by the if-statement. In my case, all cases should be handled with the if and else-if clauses, so I'm confused as to why it needs the else clause. – chaotic Mar 28 '21 at 02:43
  • 1
    Because the compiler doesn't carry around an SMT solver and try to check that your branches are exhaustive, like I said above. – kopecs Mar 28 '21 at 02:47
  • Gotcha, that makes sense then. I'll adapt the code around that then. – chaotic Mar 28 '21 at 02:49
  • 1
    Btw, I have no idea how Rust handles recursion internally, but I would have thought it's certainly more idiomatic in a non-functional language (even one like Rust that borrows some ideas from functional languages) to write a factorial function with a loop rather than recursively. – Robin Zigmond Mar 28 '21 at 10:36
  • If you want to be super explicit about it, I guess you could [`match` on the result of `cmp`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0d789f378c5374d4a38c6bb68af61b40). But even if you use `if`-`else`, [don't use `result` and `return`; simply return the value of the `if` expression](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fbe5c1b216a9b2faf90933bbb5f01b49). – trent Mar 28 '21 at 12:32

1 Answers1

6

As the error message says, if an if expression doesn't have an else then the type of the expression is (). This is because the expression can only have one type, and there is no sensible default value in the general case for if the condition evaluated to false - that's what else is for!

In your case, the compiler could have figured out that the two predicates are in fact exhaustive. It doesn't, and that really is just how it is. If the compiler could detect exhaustiveness in this case it would be weird if it couldn't also detect it in other "obvious" cases. But predicates can be arbitrary expressions and it would be impossible to implement the check in the general case.

In this example, the compiler would have to analyse the body of the random function to know if the predicates are exhaustive or not:

// random(x) might return a different value each time it's called
if random(x) > 1 {
    1
} else if random(x) <= 1 {
    2
} // Not exhaustive!

Being consistent seems like the best choice for the language, since you can always add an else at the end.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204