Confusion as a new rustacean

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?

Please read our post on code formatting and syntax highlighting and edit your post to use proper code formatting.

You can have semicolons without having a statement. This is valid Rust code.

fn main() {
    ;
    let a = 5;
    ;
}

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...

3 Likes

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.

2 Likes

I'm not sure I like it either...
It seems to lack consistency and makes people wonder...

I'm a newcomer, maybe there are cases where it is needed to be this way.

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).

It does indeed look cleaner without the commas.

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 :wink:

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.

Thanks for clarifying this.

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 {}
}

However that's just my personal preference.

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.

Thanks for the clarification!

Unless it's something like this, right?:

let x = {
    let res = do_some_computation();
    res.x
};

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.

1 Like

Thanks you all for the information, it helps understand and is appreciated

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.