$ Rust tutorials

Chapter 04

Testing, Logging, Matching… so wird’s gemacht!

It might seem a bit early to talk about those topics but it is a good spot for Rust because these features are often used while writing initial code. We will also talk about error handling, which you will encounter sooner or later as it is quite different from most of today’s programming languages.

This chapter will not discuss things like debugging running executables (e.g., with Valgrind).

Tests and linkage attributes

Okay so first, Rust packs a built-in testing/benching framework – an approach that might seem familiar to people coming from Ruby.

In order to use it, you will need to preappend the #[test] compiler attribute (notice the # and brackets syntax) to a function that will serve as test.

Here is one:

#[test]
fn try_fetch() {
    if external::input() == -1 {
        fail!("Couldn't retrieve the result!");
    }
}

These functions – just like blocks or statements marked with the #[cfg(test)] compiler attribute – will not be part of the default compiled code. This way, you can keep your files tidy by placing the tests relating to a specific part of your program at the bottom of the corresponding file.

Doing it wrong

Test functions are blackboxes: they have no arguments nor returns.
Besides fail! that halts the program and displays the error message passed as argument, tests are often made of assertions: the assert! macro takes a boolean expression as parameter; it does nothing if it evaluates to true and fails when it’s false. assert_eq! is a variant takes 2 variables as parameters and asserts equality.

use std::ascii::OwnedStrAsciiExt;

#[test]
fn lowercase_ascii() {
    let tmp = ~"ME";
    assert_eq!(~"me", tmp.into_ascii_lower());
}

Now, we just have to call rustc --test with the name of our file, and run it.

That is how the output looks like (when everything is fine, that is):

running 1 test

test lowercase_ascii ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

There is also #[bench] for benchmarking certain functions. These features are largely used in the Rust compiler itself to avoid any regression during the development process.

Note: if you are running your program in debugging mode (that is, with RUST_LOG cmdline parameter defined to level 1/2/3/4), error! warn! info! debug! will get triggered depending on the logging level.

Moving on about error handling

Rust does not have “opt-in” error handling (well, it trys to avoid opt-in safety measures when possible); that is, it works with the use of an Option wrapper directly in the function declaration.

Here are its possible values as defined in an enum (C-like cases enumerator):

enum Option<T> {
    Some(T),
    None
}

Note: if you are coming from C++, you will recognize those <> which denote polymorphism, i.e. a method that applies to various variable types (T being a lambda type). We will go back on those notions and on the trait concept in a later chapter.

So, an Option type encapsulating a certain type of variable will return Some() if a value gets catched or None if nothing passes through it.

Now let’s go about doing error handling, what we must do is destructuring those types so that we can make use of the value if we get one and also handle the case where we would not get anything returned.

If you are a C user, you are probably thinking about the use of the switch pattern?
Well, Rust has a more powerful tool called match, for pattern matching.

The mainline Rust documentation has a good example to show its most basic innards:

match my_number {
    x if x < 0 => println!("something lower than zero"),
    0          => println!("zero"),
    1 | 2      => println!("one or two"),
    3..10      => println!("three to ten"),
    _          => println!("something else")
}

Something that must be noted is that match must handle all possible cases for it to compile.
The wildcard _ means: “for all (other) possible cases”, so you will often have to use it as the last option, unless you are, for example, matching over all the individual components of an enum.

Here is how we would match against an Option value:

match optional_arg {
    Some(val) => val,
    None => () // `nil`, the empty type
}

If you remember, about assignments:

We said that match was not the only way to destructure an Option type before, and there is another way involving Rust’s functional capatibilities.

let optional_arg: Option<int> = get_option_val();

// this will remove the `Option` wrapper,
// i.e. return the value contained in `Some()` or fail
optional_arg.unwrap();

// this will unwrap or fail with the specified message in the `None` case
optional_arg.expect("No value returned!");
// this will return the value passed as parameter when encountering `None`
optional_arg.unwrap_or(3);

Note: as you can see, error handling is not a big deal in the sense that you can simply call an .unwrap() if you do not care much about the failing case.

This does not give you as much possibilities as with match but it covers the common use case of Option. There is also .is_some() or .is_none() if you want to do conditionals.

Appendix: Getting decent Results

There is a variant to the Option type, called Result:

enum Result<T, E> {
    Ok(T),
    Err(E)
}

It is quite different from Option:

Similarly to Option, Result has the is_ok() and is_err() methods.

Appendix: Can you match it?

Pattern matching is pretty powerful, noticeably when coupled with the use of tuples.
Like in Python, tuples are heterogenous lists of elements (that is, with different, unsequenced element types).

Tuples come also in handy, for example, when returning multiple values from a function:

fn main() {
    // type is ambiguous here and needs to be annotated
    let f: f32 = 1.0e-2;

    let (mantissa, exponent, sign) = f.integer_decode();
    println!("{}", mantissa);
}

As an example, here is an implementation of FizzBuzz using pattern matching:

Print numbers from 1 to 100, but for multiples of three print “Fizz” instead and for the multiples of five print “Buzz”; for multiples of both print “FizzBuzz”.

fn main() {
    for i in range(0u, 101) {
        match (i % 3 == 0, i % 5 == 0) {
            (true, true)   => println!("Fizz Buzz"),
            (true, false)  => println!("Fizz"),
            (false, true)  => println!("Buzz"),
            (false, false) => println!("{}", i)
        }
    }
}

If you want to see more on this, there is a whole article about various ways to implement this program in Rust.

Note: you can match through ref x instead of x to get a reference instead of a value (i.e. get a pointer referencing x) – some functions will need referencing.