I just started learning Rust and there are some things that confuses me.
I read the Chapter 2 of the book, Programming a Guessing Game and some parts of the code are making me wonder what is right or not.
Example:
Here I realized that I could end the match expression with a ;
loop {
...
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
} // <=== here I could end it with ;
}
Or here, as I wrote the code by myself, I realized I could write this and rustfmt and Clippy seem to be fine with it:
match guess.cmp(&secret_number) {
Ordering::Less => {
println!("Too low!") // without semicolon ;
} // without coma ,
Ordering::Greater => {
println!("Too high!")
}
Ordering::Equal => {
println!("You win!!!");
break;
}
}
I find it confusing, I don't know if it's good or wrong, if it could cause future breaking changes ...
Can anyone help me understand?
Having ; without any code does not confuse me, I've seen it in javascript and c#, what confuses me is sometime I can leave an expression without it, and sometime I must have a ending statement ;
I understand that the return value of a function must be an expression (without ; )
// as in this exemple
// the statement assigning the value have ;
// the expression returning the value does not
fn square(n: i32) -> i32 {
let result = n * n;
result
}
But in the match expression, I can have multiple arms without ; and it confuses me. I also don't understand how the branches work without ,
Is this because I don't have a variable assigned to the returned value?
A semicolon in Rust turns the value and type of an expression into (). So for example 5 evaluates to 5 whereas 5; evaluates to ().
println!(...) already evaluates to () even without a semicolon since it doesn't return anything. So putting a semicolon behind it is optional and only necessary if you want to sequence other statements after it (which is the second use of a semicolon).
match evaluates to the type that all arms return, so since you are using println as the expression inside of your match, the match expression itself will also evaluate to (). So no need to put a semicolon (again, unless you want to sequence other statements after it).
Example:
match x {
0 => println!("Test1"), // comma required. semicolon forbidden (not sure why)
1 => { // block optional
println!("Test2") // semicolon optional
}, // comma optional
2 => { // block required since we have multiple statements
println!("Test3"); // semicolon required to sequence another statement
println!("Test4") // semicolon optional
}, // comma again optional
_ => { // block required in order to be able to use a semicolon (not sure why)
5; // semicolon required to conform with the type of the other match arms
}
} // semicolon optional unless you want to sequence another statement
Edit: the comma after match arms seems to be optional when the arm consists of a block {...}. Not sure I like that...
I love it. It may feel a little unusual at first, but in the very common case where all your match expressions are blocks, it's really nice to be able to leave out all those commas, which are in no way needed for disambiguation.
Yeah. I never internalized the rules for when a comma was required after a match arm and when it was not. So I always just put it and rustfmt would strip the superfluous ones out for me (so I guess the preferred style is to leave them out if possible).
Is there any configuration rules for rustfmt or it's a community decision and there is only one style?
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Equal => {
println!("You win!");
break;
} // <=== the comma here has been removed
Ordering::Greater => println!("Too big!"), // <=== this one it still there
}
Anyway I can live with that, I tend to ask way too many questions when I learn new things
This is not strictly correct. 5; is a statement, not an expression. You can't evaluate 5; on its own. It has to be a part of a block statement or a function body, etc. For example, { 5; } indeed evaluates to (), not because 5; evaluates to (), but because the block doesn't have a final expression.
Rust is very lenient about extra commas and certain other syntax:
use std::fmt::{Debug,};
fn foo<T: Debug +,>(x: T,) -> () {
let z = (x); //Has type T
let z = (z,); //Has type (T,) AKA the 1-tuple
let z = (z,0,); //Has type (T, {integer})
}
struct Foo(usize,);
struct Bar {
x: usize,
}
fn main() {
let _ = Foo {
0: 10usize,
};
let _ = Foo(10usize,);
let _ = Bar {
x: 20usize,
};
if true {};
}
Although sometimes it changes the type of an expression (As with the 1-tuple in foo). Playground.
However, my personal taste for this specific code example would be the following:
use std::fmt::Debug;
fn foo<T: Debug>(x: T) {
let z = x; //Has type T
let z = (z,); //Has type (T,) AKA the 1-tuple
let z = (z,0); //Has type (T, {integer})
}
struct Foo(usize);
struct Bar {
x: usize,
}
fn main() {
let _ = Foo(10usize);
let _ = Foo(10usize);
let _ = Bar {
x: 20usize,
};
if true {}
}
Commas are not required after blocks, and semicolons are not recommended (and sometimes not even allowed) after blocks. Basically, if a statement ends in }, you don't need anything more.
Yes, that's different because the let statement requires a ;. Items however forbid them if you use a block. (struct Example; compiles, but struct Example {}; will not.) I was specifically talking about match arms before. It is all a bit confusing because each language construct has different reasons for having different requirements. In match arms, the compiler is needing to detect patterns which may follow expressions, but patterns following blocks are already easy to detect. The let statement occurs in a statement-context and not a match-arm-context, so a ; is needed to ensure the statement has ended. And so on, in the most confusing manner.