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
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to