At the top level, we have a *block* (sequence of statements). We also see, that the `let` statement accepts a *block* of statements as part of an assignment. Inside a *block*, statements are typically separated by `;`, with the following exceptions.
At the top level, we have a *block* (sequence of statements). We also see, that the `let` statement accepts a *block* of statements as part of an assignment. Notice, Rust allows allow assignment to refer to the result of an `if then else` construct.
Inside a *block*, statements are typically separated by `;`, with the following exceptions.
-`if` statements, and
-`if` statements, and
-`while` statements may omit the trailing `;`.
-`while` statements may omit the trailing `;`.
Additionally Rust allows for additional `;` in between statements (but extra `;` are considered non-idiomatic and thus removed by `rustfmt`).
Additionally Rust allows for additional `;` in between statements (but extra `;` are considered non-idiomatic and thus removed by `rustfmt`).
Omitting trailing `;` for the last statement in a block renders an implicit return. This is allowed by the Rust compiler in case the statement can be interpreted as an expression.
Omitting trailing `;` for the last statement in a *block* renders an implicit return. This is allowed by the Rust compiler in case the statement can be interpreted as an expression.
## An example grammar
## An example grammar
...
@@ -37,12 +39,14 @@ The example grammar in `ast/parser.lalrpop` covers a minimal subset of Rust, suf
...
@@ -37,12 +39,14 @@ The example grammar in `ast/parser.lalrpop` covers a minimal subset of Rust, suf
Some interesting design decisions:
Some interesting design decisions:
- We treat statements that may be considered as expressions by a special rule `ExprStmt`, where we accept either `if then else` or a `block`. (This is where we likely add `match` and similar statements later.)
- We treat statements that may be considered as expressions by a special rule `ExprBlock`, where we accept either `if then else` or a `block` (statments). (This is where we likely add `match` and similar statements later.)
Recall that a statement can be a return value, thus must somehow accept an expression. A `if then else` statement is one such possible expression. Now, here is the crux, without the
Recall that a statement can be a return value, thus must somehow accept an expression. Now, here is the crux, since we want `if then else` and `blocks` (statements) to be treated as expression for assignments, it would cause ambiguities between statements as part of an expression or inside a *block*. We can resolve this by the adopting `ExprNoBlock`, inside of `stmt`. `ExprNoBlock` accepts expression besides those that are matched by `ExprBlock` (`if then else` and `block`).
-`;` is treated as a `stmt`, hence we accept *blocks* like `{; let a = 5;;;;; let b = 6;;; c}`. Notice, `;` carries no meaning besides for the optional trailing `;` of a *block* (determining the return type).
-`"let" ExprNoBlock "=" Expr ";"`, forces a trailing `;` to each assignment (along the lines of the behavior of the Rust compiler).
## Reflection
-`;` is treated as a `stmt`, hence we allow *blocks* like `{; let a=5; let b=6;;;;;}`
The Rust syntax seems somewhat arbitrarily chosen. The requirement that `let` statements must be trailed by `;`, is to my best understanding not required for soundness (the `let` could have been given a `Unit` type, similar to an assignment). This leads me to believe that the trailing `;` is rather an enforcement of style.