Does the semicolon add a value at the end of `return`?

The Rust Guide states that:

A semicolon turns any expression into an operator, discarding its value and returning it.

I thought I brought this concept to the start of the experiment:

fn print_number(x: i32, y: i32) -> i32 { if x + y > 20 { return x } x + y } 

What compiles. Then I added a semicolon at the end of the return line ( return x; ). From what I understand, this turns the string into an operator, returning the unit data type () .

However, the end result is the same.

+6
source share
2 answers

I am not 100% sure what I am saying, but that makes sense.

Another concept comes into play: reachability analysis. The compiler knows that what follows the return is unreachable. For example, if we compile this function:

 fn test() -> i32 { return 1; 2 } 

We get the following warning:

 warning: unreachable expression --> src/main.rs:3:5 | 3 | 2 | ^ | 

The compiler can ignore the “true” branch of the if if it ends with the return , and only considers the “false” branch when determining the type of the if .

You can also see this behavior with diverging functions . Divergent functions are functions that do not return normally (for example, they always fail). Try replacing the return fail! macro fail! (which expands to call the divergence function). In fact, return also considered divergent; this is the basis of the above reachability analysis.

However, if the actual expression () exists after the return , you will receive an error message. This function:

 fn print_number(x: i32, y: i32) -> i32 { if x + y > 20 { return x; () } else { x + y } } 

gives the following error:

 error[E0308]: mismatched types --> src/main.rs:4:9 | 4 | () | ^^ expected i32, found () | = note: expected type `i32` found type `()` 

In the end, it seems that diverging expressions (which include return expressions) are handled differently by the compiler when they are followed by a semicolon: the statement is still diverging.

+2
source

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.

+4
source

Source: https://habr.com/ru/post/976922/


All Articles