[rust-dev] syntactic sugar for blocks in expressions

Niko Matsakis niko at alum.mit.edu
Thu Dec 29 12:48:34 PST 2011


I've been working for a while now on making it possible to use the 
syntactic sugar for blocks within expressions.  I've been doing various 
iterations, trying to find the formulation that was both (a) easy enough 
to explain and (b) made minimal changes to the existing language syntax 
/ code.  Although I think my changes only affect corner cases, they are 
not backwards compatible, so I wanted to run the proposal by the list 
before pushing it to master.

First, the goal of the change is to allow expressions like:

     let v = vec::map(v) { |e| ... };

Currently, these must be written:

     let v = vec::map(v, { |e| ... });

The syntactic sugar which allows the final block argument to appear 
outside the parentheses is only currently permitted for top-level 
statements.  Furthermore, the result of such a sugared statement is 
*always* ignored.  So something like this would parse but not type check:

     fn foo(v: [T]) -> bool {
         vec::any(v) { |e| test_some_condition(e) }
     }

Here the `vec::any(v) {||}` call would not be interpreted as the result 
of the block but rather as a call whose result should be discarded, 
similar to how a `while` loop would be interpreted.

To fix I made the following changes:

1. Dual-mode expressions like `if`, `while`, `do-while`, blocks, and of 
course sugared calls cannot be followed by binary operators or further 
calls when they appear at the top level (i.e., not nested within some 
other statement or expression).

2. All blocks may have tail expressions.  The type checker will 
guarantee that the tail expressions have unit type when they appear in 
the body of a while loop or other similar context. This used to be 
enforced by the parser.

Across the entire compiler, two changes were required as a result of 
these rules.  As it happens, one change was required by rule #1 and one 
by rule #2, so they also serve as a good way to explain the rules.

## Change #1: combining a dual-mode expression with a binary operator ##

This function

     fn companion_file(prefix: str, suffix: option::t<str>) -> str {
         alt suffix {
           option::some(s) { fs::connect(prefix, s) }
           option::none. { prefix }
         } + ".rs"
     }

no longer parses.  This is because the `alt suffix {...}` is parsed as a 
statement because it begins the statement.  Therefore, the `+".rs"` is 
parsed as an expression, and `+` is not a legal unary operator.  This 
function had to be changed as follows:

     fn companion_file(prefix: str, suffix: option::t<str>) -> str {
         ret alt suffix {
           option::some(s) { fs::connect(prefix, s) }
           option::none. { prefix }
         } + ".rs";
     }

Here I used a `ret` to place the `alt` into expression position.  You 
could also have used parentheses:

     fn companion_file(prefix: str, suffix: option::t<str>) -> str {
         (alt suffix {
           option::some(s) { fs::connect(prefix, s) }
           option::none. { prefix }
         } + ".rs")
     }

## Change #2: ignoring the result value ##

The following function from rope.rs no longer type checks:

     fn iter_chars(rope: rope, it: block(char)) {
         loop_chars(rope) {|x|
             it(x);
             true
         }
      }

The type check fails because the `loop_chars()` function returns a 
boolean value.  The call appears in the tail position of the function 
`iter_chars()` which has a unit return type.  So there is a type error 
"expected `()` but found`boolean`".  The fix is simply to introduce an 
explicit semicolon to ignore the result:

     fn iter_chars(rope: rope, it: block(char)) {
         loop_chars(rope) {|x|
             it(x);
             true
         };
      }

## More details ##

If you want more details, I wrote up the various options I considered 
here: 
<http://smallcultfollowing.com/babysteps/blog/2011/12/29/block-sugar-in-expressions/>

Ok, that's it, thoughts?


Niko


More information about the Rust-dev mailing list