Sunday, April 20, 2014

Rust for C++ programmers - part 2: control flow

If

The `if` statement is pretty much the same in Rust as C++. One difference is that the braces are mandatory, but brackets around the expression being tested are not. Another is that `if` is an expression, so you can use it the same way as the ternary `?` operator in C++ (remember from last time that if the last expression in a block is not terminated by a semi-colon, then it becomes the value of the block). There is no ternary `?` in Rust. So, the following two functions do the same thing:
fn foo(x: int) -> &'static str {
    let mut result: &'static str;
    if x < 10 {
        result = "less than 10";
    } else {
        result = "10 or more";
    }
    return result;
}

fn bar(x: int) -> &'static str {
    if x < 10 {
        "less than 10"
    } else {
        "10 or more"
    }
}
The first is a fairly literal translation of what you might write in C++. The second is in better Rust style.

You can also write `let x = if ...`, etc.

Loops

Rust has while loops, again just like C++:
fn main() {
    let mut x = 10;
    while x > 0 {
        println!("Current value: {}", x);
        x -= 1;
    }
}
There is no do...while loop in Rust, but we do have the `loop` statement which just loops forever:
fn main() {
    loop {
        println!("Just looping");   
    }
}
Rust has `break` and `continue` just like C++.

For loops

Rust also has `for` loops, but these are a bit different. Lets say you have a vector of ints and you want to print them all (we'll cover vectors/arrays, iterators, and generics in more detail in the future. For now, know that a `Vec<T>` is a sequence of `T`s and `iter()` returns an iterator from anything you might reasonably want to iterate over). A simple `for` loop would look like:
fn print_all(all: Vec<int>) {
    for a in all.iter() {
        println!("{}", a);
    }
}
If we want to index over the indices of `all` (a bit more like a standard C++ for loop over an array), you could do
fn print_all(all: Vec<int>) {
    for i in range(0, all.len()) {
        println!("{}: {}", i, all.get(i));
    }
}
Hopefully, it is obvious what the `range` and `len` functions do.

Switch/Match

Rust has a match expression which is similar to a C++ switch statement, but much more powerful. This simple version should look pretty familiar:
fn print_some(x: int) {
    match x {
        0 => println!("x is zero"),
        1 => println!("x is one"),
        10 => println!("x is ten"),
        y => println!("x is something else {}", y),
    }
}
There are some syntactic differences - we use `=>` to go from the matched value to the expression to execute, and the match arms are separated by `,` (that last `,` is optional). There are also some semantic differences which are not so obvious: the matched patterns must be exhaustive, that is all possible values of the matched expression (`x` in the above example) must be covered. Try removing the `y => ...` line and see what happens; that is because we only have matches for 0, 1, and 10 and obviously there are lots of other ints which don't get matched. In that last arm, `y` is bound to the value being matched (`x` in this case). We could also write:
fn print_some(x: int) {
    match x {
        x => println!("x is something else {}", x)
    }
}
Here the `x` in the match arm introduces a new variable which hides the argument `x`, just like declaring a variable in an inner scope.

If we don't want to name the variable, we can use `_` for an unnamed variable, which is like having a wildcard match. If we don't want to do anything, we can provide an empty branch:
fn print_some(x: int) {
    match x {
        0 => println!("x is zero"),
        1 => println!("x is one"),
        10 => println!("x is ten"),
        _ => {}
    }
}
Another semantic difference is that there is no fall through from one arm to the next.

We'll see in later posts that match is extremely powerful. For now I want to introduce just a couple more features - the 'or' operator for values and `if` clauses on arms. Hopefully an example is self-explanatory:
fn print_some_more(x: int) {
    match x {
        0 | 1 | 10 => println!("x is one of zero, one, or ten"),
        y if y < 20 => println!("x is less than 20, but not zero, one, or ten"),
        y if y == 200 => println!("x is 200 (but this is not very stylish)"),
        _ => {}
    }
}
Just like `if` expressions, `match` statements are actually expressions so we could re-write the last example as:
fn print_some_more(x: int) {
    let msg = match x {
        0 | 1 | 10 => "one of zero, one, or ten",
        y if y < 20 => "less than 20, but not zero, one, or ten",
        y if y == 200 => "200 (but this is not very stylish)",
        _ => "something else"
    };

    println!("x is {}", msg);
}
Note the semi-colon after the closing brace, that is because the `let` statement is a statement and must take the form `let msg = ...;`. We fill the rhs with a match expression (which doesn't usually need a semi-colon), but the `let` statement does. This catches me out all the time.

Motivation: Rust match statements avoid the common bugs with C++ switch statements - you can't forget a `break` and unintentionally fall through; if you add a case to an enum (more later on) the compiler will make sure it is covered by your `match` statement.

Method call

Finally, just a quick note that methods exist in Rust, similarly to C++. They are always called via the `.` operator (no `->`, more on this in another post). We saw a few examples above (`len`, `iter`). We'll go into more detail in the future about how they are defined and called. Most assumptions you might make from C++ or Java are probably correct.

20 comments:

Anonymous said...

Are the semicolons in the match statements in the last code snippet intentional? (The ones after each => arrow.)

njn said...

> 0 | 1 | 10 =>; println!("x is one of zero, one, or ten"),

Is the ';' extraneous?

vegai said...

The semicolons in the match examples seem to be wrong. rustc says "error: unexpected token: `;`"

Demizide said...

All but the semicolon on the closing brace of the let statement are erroneous.

Anonymous said...

>Rust has a match expression which is similar to a C++ statement

Did you mean 'similar to a C++ switch statement'?

Nick Cameron. said...

Sorry about the formatting bugs. Should all be fixed now - I've been having a real nightmare formatting (and re-formatting) this blog post :-(

Josh Tumath said...

As always, loving your posts on Rust. The tutorial on rust-lang.org is helpful, but its much easier to follow your guide here.

The only thing I haven't liked about Rust is some of its syntax. It uses a lot of abbreviations in its keyword and libraries. As a Computer Science undergrad with a background in Java, I guess I'm more used to long camelcase identifier names. What was the reasoning behind why Rust uses so many abbreviations?

Pic said...

I have a similar question to Josh:
why
let x: int = 0;
instead of
int x = 0;
or
fn foo() -> int
instead of
fn int foo()
You cut down on some of the syntax, while adding lots of your own.

Janus Troelsen said...

How does one distinguish a bitwise or of 0, 1 and 10 in a pattern match, with a pattern matching 0, 1 or 10? Braces?

Josh Tumath said...

@Pic
Actually, that wasn't what I was talking about at all. :)

What I meant was keywords such as "pub" being used instead of "public" or "str" instead of "string". It saves on typing, but I don't think that's a good enough benefit to sacrifice readability.

Pic said...

@Josh

I thought both our questions are about syntax, that's why I said they were similar, though I may be wrong :p

Havvy said...

The short one-syllable names are readable. Just treat them as new words that derive from the English words but with different semantics. In a way, it allows you to define 'pub' as public without taking in the full connotation of the word 'public' outside of the programming world. They also scan faster than the multi-syllable variants. The only one I don't care for is 'str' personally.

As for why the return type is at the end of the function, that is because it reads easier to see input goes to output, especially when you start using closures. Look at Haskell types for example.

Nick Cameron. said...

@Josh - C++ syntax is quite a bit more succinct than Java's, adn Rust goes a step further. I think it is mostly a matter of taste and balance - easy to read, easy to write is the goal.

I found Rust too succinct when I started (too many symbols, too many abbreviations), but it only took a few weeks to get comfortable with it. Now I like the terseness.

Nick Cameron. said...

@Pic - we need the `let` keyword to distinguish between assignment and re-defintion (Rust allows you to redefine variables). `let` is actually more powerful than just definition - it allows pattern matching, so using `:=` is a non-starter too.

For function signatures, Rust maintains a regular keyword, name, other pattern to all its language features, so we can't have the return type before the name as in Java and C++. Using `->` is common in functional languages, so seems like a good choice.

Nick Cameron. said...

@Janus - yes, use parenthesis

abetting niuddit said...

So how /would/ you do a do / while? Just:

if should not continue {break;}

?

Or is there a variant of one of the others that lets you do it? do / while is uncommon, but when you do need it it'd be nice to have a clean form available.

Valvar said...

In the while loop, shouldn't it be:
let mut x = 10i

The compiler was throwing me errors without the suffix, so I checked up some other examples...

Thanks a lot for these fantastic pieces!

Nick Cameron. said...

@Valvar you are correct. It is actually a recent change to the compiler - the blog post was correct at time of writing (we used to infer the kind of integer). Now you must explicitly mark it.

Affity Solutions said...

Thanks for your ideas. You can also find the details on Affity Solutions, at the System Programming. The main object of the Affity Solutions is to provide quality web services and is among the few software development company in Nagpur.

Rohit Joshi said...

Thanks!! I am coming from C++ background and learning Rust using your blog post is helping!!