Setup dự án

Dùng cargo để setup dự án mới

cargo new guessing_game
cd guessing_game

Nhận input

Chỉnh sửa file src/main.rs như dưới đây.

use std::io;

fn main() {
    println!("Welcome to Guessing Game");
    
    println!("Enter your guess: ");
    
    let mut guess = String::new();
    
    io::stdin.read_line(&mut guess)
        .expect("Error reading stdin");
    
    println!("You guessed {}", guess);
}

Dòng đầu tiên, use std::io sẽ có tác dụng tương tự với việc import một thư viện vào dự án. Với std::io, chương trình sẽ có thể nhận được input.

fn main() là tạo ra một hàm main. Hai dòng println!() là để hiện chuỗi ra màn hình.

Lưu input vào một biến

Để tạo ra một biến trong Rust, dùng lệnh let

let foo = bar;

Tuy nhiên, mặc định, khi tạo ra một biến như cách trên, biến đó được xem là bất biến - immutable (có thể hiểu là một constant). Do đó khi muốn tạo một biến có thể thay đổi được giá trị, phải có thêm keyword mut trước tên biến.

let     a = 10;  //immutable
let mut b = 10;  //muttable

Note: Để comment trong Rust, dùng cặp dấu //

Trong chương trình Guessing Game, biến guess có keyword mut nên giá trị của nó có thể dược thay đổi và biến này là một instance của lớp String. Việc gọi hàm String::new() sẽ trả về một instace của lớp String và gán nó cho biến guess bằng dấu =.

Note: Cú pháp :: trong String::new() cho biết rằng new là một hàm dựa vào String. Ở một số ngôn ngữ khác, hàm này được gọi là phương thức tĩnh (static method).

Tóm gọn lại, câu lệnh let mut guess = String::new() sẽ tạo ra một biến có thể thay đổi, tên là guess và là một instace của String.

Handle các lỗi tiềm ẩn ra bằng Result type

Ở câu lệnh io::stdin.read_line(&mut guess).expect("Error reading stdin") có phần .expect(...).

Do io::stdin.read_line(...) sẽ trả về io::Result, một giá trị có dạng enum và có giá trị Ok hoặc Err. Rust cần phải xử lý nếu có lỗi xảy ra dựa vào io::Result này.

Khi io::ResultOk, điều này có nghĩa câu lệnh chạy thành công.

Ngược lại, đối với Err, câu lệnh thực thi thất bại và Err sẽ chứa các thông tin như nguyên nhân làm cho nó thất bại.

Instace của io::Result có một phương thức có thể gọi, đó là expect(). Nếu Result có giá trị Err, except() sẽ được gọi và in ra giá trị được truyền vào. Nếu ResultOk, except() sẽ lấy giá trị mà Ok đang giữ và trả về giá trị đó.

In giá trị bằng placeholder

Trong lệnh println!("You guessed {}", num), {} là placeholder.

Một lệnh println có thể chứa nhiều placeholder tương ứng với số lượng biến mà sẽ được truyền giá trị vào placeholder đó. Do đó, khi in ra nhiều giá trị với placeholder, câu lệnh sẽ như sau:

let a = 10;
let b = 50;

println!("Value of a = {}, value of b is {}", a, b);

Chạy thử chương trình vừa tạo

Để chạy chương trình vừa tạo, dùng lệnh cargo run

$ cargo run
   Compiling guessing-game v0.1.0 (/home/codechovui/rust/guessing-game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/guessing-game`

Guess the number!
Please enter your guess: 
1001
You guessed 1001

Vậy là chương trình đã hoàn thành một phần: nhận input, lưu và in input ra màn hình.

Tạo ra một số ngẫu nhiên

Rust không cung cấp random sẵn có. Tuy nhiên đội ngũ Rust có cung cấp một crate để tạo ra số ngẫu nhiên tại https://crates.io/crates/rand

Thêm dependency này vào Cargo.toml ngay dưới phần [dependencies] đã được tạo sẵn.

[dependencies]

rand = "^0.8.5"

Note: Cargo có thể hiểu được Simantic Versioning (SemVer).

Lưu lại file, và chạy cargo build để build ra một file thực thi. Lúc này, Cargo sẽ tải các dependencies được liệt kê trong Cargo.toml mà chưa được tải xuống từ trước.

Đây là một chương trình tạo ra số ngẫu nhiên từ 1 đến 100, sử dụng crate vừa được cài đặt.

use std::io;
use rand::Rng;

fn main() {
    let random_number = rand::thread_rng().gen_range(1..101);
    println!("The random number is: {}", random_number);
}

Áp dụng vào chương trình Guessing Game, file src/main.rs sẽ giống như sau:

use rand::Rng;
use std::io;

fn main() {
    println!("Guess the number!");

    // generate random number
    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("Please enter your guess: ");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read input");

    println!("You guessed {}", guess);
}

So sánh input

Dùng std::cmp::Ordering để so sánh.

use std::cmp::Ordering;
use std::io;

fn main() {
    let mut number = String::new();
    io::stdin().read_line(&mut number).expect("Error reading input");
    let secret = 53;

    // Convert String to number
    let number: u32 = number.trim().parse().expect("Must be a number");

    match secret_number.cmp(&guess) {
        Ordering::Less => println!("Less"),
        Ordering::Greater => println!("Greater"),
        Ordering::Equal => println!("That's right!"),
    }
}

Dùng if else để so sánh.

use std::io;

fn main() {
    let mut number = String::new();
    io::stdin().read_line(&mut number).expect("Error reading input");
    let secret = 53;

    // Convert String to number
    let number: u32 = number.trim().parse().expect("Must be a number");

    if number > secret {
        println!(">");
    } else if number < secret {
        println!("<");
    } else {
        println!("=")
    }
}

Áp dụng chương trình trên vào Guessing game, file main.rs sẽ như sau.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    // generate random number
    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("Please enter your guess: ");

    let mut guess = String::new();

    io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read input");
    
    // Convert string to number
    let guess: u32 = guess.trim().parse().expect("Input must be a number");

    println!("You guessed {}", guess);
    match secret_number.cmp(&guess) {
        Ordering::Less => println!("Less"),
        Ordering::Greater => println!("Greater"),
        Ordering::Equal => println!("That's right!"),
    }
}

Handle input không hợp lệ

Khi chuyển từ string về number, sẽ có trường hợp input là một chuỗi chữ. Để tránh gây crash, trường hợp này sẽ được giải quyết như sau.

fn main() {
    // trying to convert string to number
    let number = match the_string.trim().parse() {
        Ok(num) => num,
        Err(err) => err,
    };
}

Câu lệnh trên có nghĩa là nếu parseOk, thì trả về giá trị số vừa convert. Còn nếu Err thì trả về err.

Áp dụng vào chương trình guessing game, file main.rs sẽ như sau:

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    // generate random number
    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("Please enter your guess: ");

    let mut guess = String::new();

    io::stdin()
    .read_line(&mut guess)
    .expect("Failed to read input");
    
    // Convert string to number
    let guess: u32 = match guess.trim().parse() {
        Ok(num) => num,
        Err(err) => err,
    };
    
    println!("You guessed {}", guess);
    match secret_number.cmp(&guess) {
        Ordering::Less => println!("Less"),
        Ordering::Greater => println!("Greater"),
        Ordering::Equal => println!("That's right!"),
    }
}

Dùng vòng lặp

Để nhập được input nhiều lần sau khi đoán sai, một vòng lặp sẽ giải quyết vấn đề này.

Khởi tạo vòng lặp

Từ khóa loop {} sẽ tạo ra một vòng lặp vô tận. Áp dụng vào chương trình Guessing game, file main.rs sẽ như sau.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    // generate random number
    let secret_number = rand::thread_rng().gen_range(1..101);
    
    // create a infinite loop
    loop {
        println!("Please enter your guess: ");

        let mut guess = String::new();

        io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read input");
        
        // Convert string to number
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(err) => continue,
        };
    
        println!("You guessed {}", guess);
        match secret_number.cmp(&guess) {
            Ordering::Less => println!("Less"),
            Ordering::Greater => println!("Greater"),
            Ordering::Equal => println!("That's right!"),
        }
    }
}

Do dùng vòng lặp, câu lệnh handle input sẽ như sau

// ...
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(err) => continue,
        };
// ...        

Có nghĩa, nếu nhập đúng thì trả về số, còn không thì chạy vòng lặp mới, nhận input mới.

Dừng chương trình khi người dùng đoán đúng

Lệnh break sẽ được dùng để thoát khỏi vòng lặp. Trong câu lệnh so sánh, thêm lệnh break khi người dùng đoán đúng số.

// ...
        match secret_number.cmp(&guess) {
            Ordering::Less => println!("Less"),
            Ordering::Greater => println!("Greater"),
            Ordering::Equal => {
                println!("That's right!");
                break;
            }
        }
// ...

Do đó, toàn bộ chương trình sẽ như sau.

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    // generate random number
    let secret_number = rand::thread_rng().gen_range(1..101);
    
    // create a infinite loop
    loop {
        println!("Please enter your guess: ");

        let mut guess = String::new();

        io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read input");
        
        // Convert string to number
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(err) => continue,
        };
    
        println!("You guessed {}", guess);
        match secret_number.cmp(&guess) {
            Ordering::Less => println!("Less"),
            Ordering::Greater => println!("Greater"),
            Ordering::Equal => {
                println!("That's right!");
                break;
            }
        }
    }
}