Why does "if return" compile in Rust? - rust

I found some strange Rust code in dtolnay's mind-bending Rust quiz. Apparently, this is a valid Rust program (playground):
fn main() {
if return { print!("1") } {}
}
According to the Rust docs:
The syntax of an if expression is a condition operand, followed by a consequent block, any number of else if conditions and blocks, and an optional trailing else block. The condition operands must have the boolean type.
To me it means that the return statement must somehow evaluate as a boolean, otherwise the code wouldn't compile. But that explanation seems outlandish, and I suspect there must be something else going on.
So why does if return compile at all?

It is for the same reason the following code compiles:
fn create_string() -> String {
std::process::exit(0);
}
The body of the function above does never return a String value, however, since std::process::exit() is a divergent function (i.e., its return type is !), it results in valid code: The control flow will never reach the point where a String value has to be produced.
The same applies to panic!(). The following code, which replaces your return with panic!(), also compiles for the same reason:
fn main() {
if panic!() { print!("1") } {}
}

You're looking for the never type, which is the result of the return expression.
And while it compiles, it does generate a warning:
warning: unreachable block in `if` or `while` expression
|
2 | if return { print!("1") } {}
| ---------------------- ^^ unreachable block in `if` or `while` expression
| |
| any code following this expression is unreachable

Related

Rust macros: match argument as closure but differentiate between non-closure-expression and closure-expressions

I want to create a macro that can be used like this
generate_code!(println!("macro argument is expression"));
generate_code!(|| println!("macro argument is closure"));
Unfortunately both macro arguments match as "expr". My macro should be smart enough to add || in the first case. Is this possible in Rust?
macro_rules! generate_code {
// for `generate_code!(println!())`
($expression:expr) => {do_sth(|| $expression)};
// for `generate_code!(|| println!())`
($closure:expr???) => {do_sth($closure)};
}
You can match the closure tokens explicitly:
macro_rules! generate_code {
(|| $expression:expr) => {do_sth(|| $expression)};
(move || $expression:expr) => {do_sth(move || $expression)};
($expression:expr) => {do_sth(|| $expression)};
}
(I've included the move closures as well, which are often used.)
For example, this runs as expected:
fn do_sth(f: impl FnOnce()) {
f();
}
fn main() {
generate_code!(println!("macro argument is expression"));
generate_code!(|| println!("macro argument is closure"));
generate_code!(move || println!("macro argument is move closure"));
}
Note that this approach could lead to surprising results because it doesn't really detect closures, it just detects the literal || token sequence. If you disguise a closure as generate_code!((|| println!("xxx"))) or if the closure takes arguments, as generate_code!(|foo| println!("{}", foo)), the expression will be treated as a non-closure expression and will be stuffed in the closure. Depending on the bounds on type accepted by do_sth, the result be either a cryptic compilation error such as "expecting unit type, found closure" or (worse) successful compilation and unwanted behavior.

Why does Rust not infer this closure's return type?

The Rust compiler is usually able to infer the type of an expression that is returned from a closure:
fn main() {
let a_closure = |num|{
num+1.0
};
println!("{}", a_closure(1.0));
}
But the compiler is unable to infer the type when I define the same closure using a return statement:
fn main() {
let a_closure = |num|{
return num+1.0
};
println!("{}", a_closure(1.0));
}
/*
error[E0308]: mismatched types
--> src/main.rs:3:9
|
3 | return num+1.0
| ^^^^^^^^^^^^^^ expected `()`, found `f64`
*/
I'm surprised that Rust can't infer the type here: is it possible to use a return statement in a closure without preventing the compiler from inferring its return type?
This is due to the lack of a semicolon. When you have no semicolon, the last expression is returned, and this last expression is return num + 1.0. Since a return statement makes the program jump somewhere, else, it's value can be anything, for example:
fn main() {
let a: String = return;
}
However, if the compiler sees no direct type assigned to it, it will pick the type () as the value of the return statement.
So what happened is:
The compiler sees the last expression in the closure, and assigns it the type () by default.
The compiler then sees an explicit return inside the closure, which returns the type i32.
So since there are two attempts to return from the closure, and they each return different types, that's a type mismatch.

Different versions of the same function with conditional compilation in Rust

I'm trying to create two different versions of the same function, only one of which will be compiled. As an example:
#[cfg(debug_assertions)]
fn do_something(x: usize) -> usize {
x + 1
}
#[cfg(not(debug_assertions))]
fn do_something() -> usize {
0
}
This works fine and I can also call the correct version of do_something if I know which one I'm calling (In practice, the functions will do the exact same thing, the debug one just requires more info for some validation). So I can create two corresponding main functions:
#[cfg(debug_assertions)]
fn main() {
do_something(0);
}
#[cfg(not(debug_assertions))]
fn main() {
do_something();
}
But this is very clumsy, and I want to have only one version of the code that doesn't depend on debug_assertions. I'd like to do something like:
macro_rules! call {
($func:ident, $optional_arg:expr) => {{
if cfg!(debug_assertions) {
$func($optional_arg);
} else {
$func();
}
}};
}
fn main() {
call!(do_something, 0);
}
and then re-use the macro wherever I call this function, or similar ones. But this doesn't compile:
--> main.rs:16:13
|
2 | fn do_something(x: usize) -> usize {
| ---------------------------------- defined here
...
16 | $func();
| ^^^^^-- supplied 0 arguments
| |
| expected 1 argument
...
22 | call!(do_something, 0);
| ----------------------- in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
I don't understand why I get the error, since the wrong function call shouldn't even be compiled.
The error can be fixed by forcing the functions to have the same signature and simply ignoring the unnecessary argument in the release version, but that doesn't seem like the right way to go.
What would you do in this situation? Why doesn't the macro example compile?
From the reference :
cfg!, unlike #[cfg], does not remove any code and only evaluates
to true or false. For example, all blocks in an if/else expression
need to be valid when cfg! is used for the condition, regardless of
what cfg! is evaluating
Flags will be evaluated in compile time but you are doing this check at runtime. You need to use attributes to avoid the problem:
macro_rules! call {
($func:ident, $optional_arg:expr) => {{
#[cfg(debug_assertions)]
$func($optional_arg);
#[cfg(not(debug_assertions))]
$func();
}};
}

Why can I directly match an array of Options but not a variable containing an array of Options?

The following code compiles:
fn consume(_: Box<u64>) {}
let tuple = (Some(Box::new(1)), Some(Box::new(2)));
match tuple {
(Some(x), Some(y)) => {
consume(x);
consume(y);
}
_ => (),
}
The following code compiles:
fn consume(_: Box<u64>) {}
match [Some(Box::new(1)), Some(Box::new(2))] {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}
But this code does not compile:
fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match array {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}
The compiler says:
error[E0382]: use of moved value: `(array[..] as std::prelude::v1::Some).0`
--> src/main.rs:5:24
|
5 | [Some(x), Some(y)] => {
| - ^ value used here after move
| |
| value moved here
|
= note: move occurs because the value has type `std::boxed::Box<u64>`, which does not implement the `Copy` trait
Why do the first and second version compile, but not the third version?
Here's a reduced version of your code:
struct NonCopy;
fn main() {
// OK
let tuple = (Some(NonCopy), Some(NonCopy));
if let (Some(_x), Some(_y)) = tuple {}
// OK
if let [Some(_x), Some(_y)] = [Some(NonCopy), Some(NonCopy)] {}
// Fails
let array = [Some(NonCopy), Some(NonCopy)];
if let [Some(_x), Some(_y)] = array {}
}
The good news
This code works as-is when non-lexical lifetimes are enabled.
The bad news
Non-lexical lifetimes aren't stable yet.
The workaround
Explicitly transfer ownership of the array to the match or if let head expression:
let array = [Some(NonCopy), Some(NonCopy)];
if let [Some(_x), Some(_y)] = { array } {}
The explanation
The current implementation of the borrow checker is AST-based while a future implementation will be MIR-based. At a high level, you can think of this as "working on the code as I typed it" (AST) and "working on the logical flow of data in my code" (MIR).
Certain "hacks" have been added to the AST borrow checker, which is why you can successfully use an array literal but not the variable. With the MIR borrow checker, bigger hacks like these will disappear and the borrow checker will also become more precise, allowing more code to compile.
It is unexpected that the third version does not compile. The same issue occurs when matching boxed values as described here.
Citing the explanation for the error from the linked answer:
My only guess is that the ownership of the Box is moved to the first
param, the param is extracted, then the compiler tries to move it
again to the next parameter.
Replace "Box" with "array" and you get an explanation for what is going on when matching an array. One of the solutions presented in the linked answer also works for matching arrays - the use of curly braces to force a complete move of the Box/array into the match expression:
fn consume(_: Box<u64>) {}
let array = [Some(Box::new(1)), Some(Box::new(2))];
match {array} {
[Some(x), Some(y)] => {
consume(x);
consume(y);
}
_ => (),
}

Why does the compiler assume that the value of if let should be `()`?

I have the following code:
use std::collections::HashSet;
fn translate() -> Option<String> {
None
}
fn main() {
let mut found = HashSet::new();
if let Some(tr) = translate() {
found.insert(tr);
}
}
It works properly, but when I remove the semicolon after found.insert(tr), I get a compiler error:
error[E0308]: mismatched types
--> src/main.rs:11:9
|
7 | fn main() {
| - expected `()` because of default return type
...
11 | found.insert(tr)
| ^^^^^^^^^^^^^^^^- help: try adding a semicolon: `;`
| |
| expected (), found bool
|
= note: expected type `()`
found type `bool`
It doesn't matter where this code is located or whether it is the last expression of the function.
Why does the compiler assume that the expression inside the curly braces should be ()?
According to the Rust Book (emphasis mine):
The value of the expression is the value of the last expression in whichever branch was chosen. An if without an else always results in () as the value.
This gives a constraint on the expression value inside the curly braces.
This is correct since the expression type matches ():
if let Some(_) = some() {
()
};
This is correct since there's an else statement (and the types between the branches match):
if let Some(_) = some() {
true
} else {
false
};
But this is wrong:
if let Some(_) = some() {
true
};
This answer was inspired by this comment.
When you omit a function's return type, the function actually returns (). That is,
fn foo() {}
is equivalent to:
fn foo() -> () {}
If I add return (); at the end of the function I still get the same error. So I'm not even sure if that has something to do with a function return value.
An if let expression that is used as a statement must return (), unless it is the last expression in the function's body, in which case its type must match the function's return type. Since your if let doesn't have an else clause, its type must be ().

Resources