$ Rust tutorials

Chapter 05

Guess what…

Okay, now let’s do some lab work. You should (almost) have the knowledge to implement a classic litle game: guessing a secret number.

The computer (randomly) chooses a number between 1 and 100 and the user must find it; the computer will tell whether the given number is too low/high until the user finds the right one.

Alright, let’s have some prerequisities beforehand.

The first thing we will need is to have a random number in our hands.

Getting some RNG

We want an integer random number, bounded between 1 and 100. Let’s print a few of them:

// `use` imports methods into namespace
use std::rand::{task_rng, Rng};

fn main() {
    let mut cur;
    for _ in range(0, 11) {
        // nb: range upper bound is exclusive
        cur = task_rng().gen_range(1u, 101);
        println!("{}", cur);
    }
}

Then, we will have to get input from the user. Unlike print and friends, input methods are not built in Rust programs by default, i.e. they are not in the prelude.

Importing from the standard library

So, we will have to import the relevant std::io methods manually, with the use keyword that is just like import in Python or Java. #include in C is quite similar too althrough C works only on a per-file basis.
Contrariwise, Rust uses the concept of module. Similarly to Java’s packages, each file is a module, but not only: you can declare modules with the mod keyword within a file, and each one has a different namespace, which means that you will have to go through logical paths (::) of modules when importing methods.

Module names are lowercase by convention.

Note: modules are superseded by crates, which are individual compilation units (one gets compiled at a time, e.g., one library on a project makes one crate).

// We can select different functions with `{}`.
// Note: `use` must precede `mod` statement.
use foo::{bar, baz};

mod foo {
    pub fn bar();
    pub fn baz();
}

bar();
baz();

// Now, import `baz()` fn from `bonus.rs`.
mod bonus;
bonus::baz();

Note: the priv and pub attributes permit to allow/disallow access to a specific module of function.

So, back to the stdio things:

use std::io::stdin;

If you are looking for a specific function to import at some point, just look in libstd documentation or ask your way on IRC.

Note: if you wanted to import from external libraries you would have to link to them using extern crate. For example, the Rust filestack has an extra library for non-core things, which you could link to using extern crate extra; before your use statement.

Calling the user… err?

Alright, now let’s call read_line() to get input from the user. This method makes use of the Option type we just saw.

use std::io::BufferedReader;
use std::io::stdin;

fn main() {
    println!("Type something:");

    let mut reader = BufferedReader::new(stdin());

    // Note: input will end with `\n`
    match reader.read_line() {
        // Handle empty input too
        Some(~"\n") => println!("\nI never asked for this..."),
        Some(thing) => println!("\nYou typed: {}", thing),
        None        => println!("\nWell, that's unexpected!")
    }
}

On a side note: if you want a message and an input on the same line, it will take a direct access to stdout.

use std::io::stdout;

stdout.write("Type something: ".as_bytes());
println!(reader.read_line().unwrap());

Now we want to get an int out of stdin, but looking at the empty case, the program could behave strangely depending on what we are doing with it. So instead we could ask the user back for input (by a recursive call to our input function, for example) or stop with a specific error message.
Here is an example that uses a loop:

use std::io::stdin;

fn number_input() {
    println!("Please type a number: ");

    loop {
        // We need to trim the EOL `\n` char to be able to convert
        match from_str::<uint>(stdin().read_line().trim_right_chars(&'\n')) {
            Some(x) => return x,
            None => println!("I'd rather have a number.")
        }
    }
}

Okay, that should be all you need. Just do it by yourself and the compiler outputs first before looking at the doc!

Let’s add something to the game: we will count how many times the user tries and display it at the end.

The Solution (well, an implementation of it)

So here it is:

use std::io::BufferedReader;
use std::io::stdin;
use std::rand::{task_rng, Rng};

fn input_line() -> ~str {
    let mut reader = BufferedReader::new(stdin());
    loop {
        match reader.read_line() {
            Some(~"\n") => println!("\nUhm, please type something..."),
            Some(thing) => return thing,
            None => continue
        }
    }
}

fn input_number() -> uint {
    println!("Please type a number: ");

    loop {
        match from_str::<uint>(input_line().trim_right_chars(&'\n')) {
            Some(x) => return x,
            None => println!("I'd rather have a number.")
        }
    }
}

fn main() {
    println!("The computer is choosing a secret number, between 1 and 100...");
    let nbr = task_rng().gen_range(1u, 101);
    let mut cpt = 0u;

    println!("Can you guess it?");
    loop {
        cpt += 1;
        match input_number() {
            x if x < nbr => println!("Too low!"),
            x if x > nbr => println!("Too high!"),
            _ => { println!("Yes, you found it in {} tries!", cpt); break; }
        }
    }
}

We have input_line() that takes a non-empty stdin input and input_number() that ensures a non-empty converted number.