Typically, each branch in an if should be of the same type. If no type is specified for any branch, the compiler tries to find one common type:
fn print_number(x: int, y: int) { let v = if x + y > 20 { 3 // this can be either 3u, 3i, 3u8 etc. } else { x + y // this is always int }; println!("{}", v); }
In this code, 3 indicated below, but the else branch forces it to be of type int .
It sounds simple: there is a function that "combines" two or more types into a common type, or it will give you an error if this is not possible. But what if the branch was fail! ?
fn print_number(x: int, y: int) { let v = if x + y > 20 { fail!("x + y too large") // ??? } else { x + y // this is always int }; println!("{}", v); // uh wait, what the type of `v`? }
I would like to fail! did not affect other branches, this is, after all, an exceptional case. Since this pattern is fairly common in Rust, the concept of a divergent type was introduced. There is no value whose type diverges. (It is also called the “uninhabited type” or “void type” depending on the context. It should not be confused with the “unit type”, which has a single meaning () .) Since the diverging type is a natural subset of any other types, the compiler concludes that type v is just an else , int tag.
Return expression is no different from fail! for type checking. It unexpectedly escapes the current thread of execution, like fail! (but does not complete the task, fortunately). However, the divergent type does not apply to the following statement:
fn print_number(x: int, y: int) { let v = if x + y > 20 { return; // this is diverging () // this is implied, even when you omit it } else { x + y // this is always int }; println!("{}", v); // again, what the type of `v`? }
Note that the only semi-column operator x; equivalent to the expression x; () x; () . Usually a; b a; b is of the same type as b , so it would be rather strange that x; () x; () has type () only when x does not diverge and diverges when x diverges. That is why your source code did not work.
It is tempting to add a special case:
- Why don't you split
x; () x; () when does x diverge? - Why don't you accept
uint for every unproven integer literal when its type cannot be deduced? (Note: this has been so in the past.) - Why shouldn't you automatically find a generic Supertrait when combining objects with multiple attributes?
The truth is that designing a type system is not very difficult, but testing it is much more complicated, and we want a Rust type system to be reliable and durable. Some of them can happen if it is really useful, and it is proved "right" for our purpose, but not immediately.