📜 ⬆️ ⬇️

Rust: We get acquainted with the language on the example of "Guess-ki"

Let's get acquainted with Rust, having worked on a small project! We will show the basic concepts of Rust with a real example. You will learn about let , match , methods, associated functions, connecting third-party libraries and much more. We implement the classic task: the game “guess the-ka”.


  1. The program generates a random number from 1 to 100.
  2. After that asks the player to enter his guess.
  3. After that, the program notifies the player:
    • if he guessed the number, the game ends
    • if not:
      • writes, less suggested number than conceived or more.
      • proceeds to step 2.

Creating a new project


In order to create a new project:


 $ cargo new guessing_game --bin $ cd guessing_game 

The first cargo new command takes the program name ( guessing_game ) as the first argument. --bin tells Cargo to prepare a project for writing a program (as opposed to a library). Let's see what we have in the configuration file of the project - Cargo.toml


 [package] name = "guessing_game" version = "0.1.0" authors = ["Your Name <you@example.com>"] [dependencies] 

If the author information that Cargo received from your system is incorrect, correct the configuration file and save. By default, the new project contains "Hello, world".
src / main.rs


 fn main() { println!("Hello, world!"); } 

Let's build the program and run:


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 1.50 secs Running `target/debug/guessing_game` Hello, world! 

The run command is very useful when you often repeat the compile, build and run phases in succession.


Guess processing


The first part of our program will ask the user to enter his guess, process his input and verify that it is valid . To begin with, let's give the user a guess of his: Hereinafter, all changes are made to src/main.rs :


 //        stdout use std::io; fn main() { println!("Guess the number!"); println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); } 

The code carries a lot of information, so consider it gradually. To get the entered guess from the user and output it to stdout, we need to bring the io library into the global scope . io is part of the standard library (hereafter, std ):


 use std::io; 

By default, Rust brings into the program scope only a few types ( prelude ). If the prelude does not have the type you need, you should add it to the scope by manually writing the use statement. Using std::io gives us many functions for handling I / O ( IO ), including the ability to read user input. The main function (as in C and C ++) is the entry point into the program.


 fn main() { 

fn allows you to declare a new function, () indicates that the function takes no parameters, { precedes the body of the function. println! - macro ( macro ), which displays a string on the console:


 println!("Guess the number!"); //   . println!("Please input your guess."); 

Store values ​​in variables


Now create a place where user input will be stored:


 let mut guess = String::new(); 

Notice the let statement, which is needed to create variables. Could be so:


 let foo = bar; 

The code creates a variable called foo and binds it to the value bar . In Rust, the default variables are immutable. To make them editable, next to let before the variable name add mut .


 let foo = 5; //  (immutable) let mut bar = 5; // mutable() 

Now you know that let mut guess creates a variable variable guess . On the right side of the = sign is the value to which the "bind" guess is the result of calling String::new , a function that returns a new string. String is a string type (similar to string in C ++ and StringBuilder in Java), which can grow, contains text encoded in UTF-8 . :: in ::new indicates that new is an associated function with a String type. The associated function is implemented over the type, in this case over the String, and over a specific type instance. Some languages, such as C ++, call such functions static ( static ) methods. The new function creates a new empty String instance. You will see that new implemented over many types, because it is used to denote a function that creates a new value of some type. To summarize: let mut guess = String::new(); created a new variable that is “bound” ( bound ) to a new empty String instance (empty string). We have connected functions for working with IO by use std::io . Now we will call the associated function stdin :


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

If we did not write use std::io at the beginning of the program, then we could write the call to this function as std::io::stdin . The stdin function returns an instance of std::io::Stdin , which is the type that represents the pointer to the standard input stream ( standard input ). Here it is the user keyboard. The next part of our code, .read_line(&mut guess) , calls read_line on the just-received pointer ( handle ) to stdin to read the user input. We also send a single argument: &mut guess . read_line accepts from the user what comes to stdin and puts this input into the string, therefore accepts a reference to a string variable. This string argument must be mutable so that the method can change the value of this string by adding user input. & indicates that this argument ( &mut guess ) is a reference ( reference ), which allows different parts of the code to access the same data region without copying the value from this data region many times. One of the strengths of Rust is that it’s easy to use links (and without damaging the memory) with links. For now, it’s enough for us to know that links, like variables, are by default immutable. Therefore, we write &mut guess (create a changeable link), not &guess (create an immutable link).


 .expect("Failed to read line"); 

When you call a method using the syntax .foo() , it is advisable to switch to a new line so that the line of code is not too long. This will make it easier for other programmers to read the code.


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

read a little harder.


Handling completion with an error (use Result )


read_line not only reads a string, but also returns a value; here, this value is of type io::Result . Rust has several types called Result in different library modules.



 $ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused `std::result::Result` which must be used --> src/main.rs:10:5 | 10 | io::stdin().read_line(&mut guess); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: #[warn(unused_must_use)] on by default 

The compiler notifies us that we did not use the returned Result value, indicating that the program did not handle a possible error. To suppress a warning, you need to write an error handling code, but we use expect , because we want to see how the program ends with an error.


Printing values ​​through println!


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

{} is a placeholder , indicates that the resulting string will contain the value corresponding to this placeholder variable. To print multiple values, use multiple placeholders: the first placeholder ( {} ) corresponds to the first output value, the second one to the second, and so on. For example:


 let x = 5; let y = 10; println!("x = {} and y = {}", x, y); // : // x = 5 and y = 10 

Check the first part


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs Running `target/debug/guessing_game` Guess the number! Please input your guess. 6 You guessed: 6 

The program requested a number, we entered, the program output this number - everything works. Go ahead.


Secret number generation


Think of a number (from 1 to 100) that the user will try to guess. It must be a random number so that you can play the game more than once. Rust in std (standard library) does not have a module for working with random numbers, so we use a third-party library ( crate ) in terms of Rust) rand .


We use crate to get additional functionality


The crate is just a package of code on Rust. We are writing the binary crate , a regular program in the form of an executable binary file. rand - library crate , similar to .o and .so files in C - contains functions that any other crate can connect. Cargo is very convenient to use to install third-party crates. In order to start using the rand crate, we need to make the appropriate changes to Cargo.toml so that it includes rand as dependency. Make changes to the [dependencies] section


 [dependencies] rand = "0.3.14" 

In the Cargo.toml file , everything that follows the section header continues until a new section starts. The [dependencies] section is where you specify Cargo crates, on which your project code depends, and which versions you need. In this case, we have indicated that we need a rand crate with a version 0.3.14 to build the project. Cargo understands the model of semantic versioning ( Semantic Versioning ), which is one of the standards for naming different versions of programs. The number 0.3.14 (actually, this is the short form of the record ^0.3.14 ) means that any version that has an open ( public ) API is compatible with version 0.3.14. Let's collect, but with a new dependency:


 $ cargo build Updating registry `https://github.com/rust-lang/crates.io-index` Downloading rand v0.3.14 Downloading libc v0.2.14 Compiling libc v0.2.14 Compiling rand v0.3.14 Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs 

Cargo downloads dependencies from the repository ( registry ) - Crates.io . Crates.io is a place where Rust-developers post their open-source projects. After Cargo.toml downloads the index, it checks if there are any dependencies that have not been downloaded yet. If there is, then download them. In addition, it also downloads libc , because the rand crate depends on it. This means that transitive dependencies are resolved (dependency resolution) automatically. After downloading the dependencies, rustc (the Rust compiler) compiles them, then compiles the program itself, using precompiled dependencies. If you run the cargo build again, you will not see any messages. Cargo knows that the necessary dependencies are already downloaded and compiled, and knows that you have not changed the [dependencies] section in Cargo.toml . Cargo also knows that you have not changed your code, so it is not recompiled again. Cargo is completed, as there is no work that he would need to do. If you open src / main.rs and make changes, then the cargo build will compile the code of our project again, but not the compilation of dependencies, because they have not changed ( incremental compilation ).


Cargo.lock helps build reproducible builds


Cargo has tools that allow you to receive renewable assemblies, it will use only those versions of dependencies that you specified, until you specify other versions of dependencies. What happens if a rand version v0.3.15 , containing an important bug fix , and a regression that breaks your code? In order to solve this problem, the Cargo.lock file is used , which was created when cago build was first run and is now located in the root directory of the project. When you build a project for the first time, Cargo finds out the dependency numbers that meet the specified requirements (in Cargo.toml ) and writes information about them in Cargo.lock . When you build your project for the second and subsequent times, Cargo sees that Cargo.lock already exists and uses the versions that are listed there, and does not display them again, wasting time on dependency analysis. This gives us the opportunity to get reproducible builds. In other words, the project will use rand version 0.3.14 until you explicitly upgrade.


Update crate to the latest version


When you want to update the crate, Cargo provides us with the update command, which:



 $ cargo update Updating registry `https://github.com/rust-lang/crates.io-index` Updating rand v0.3.14 -> v0.3.15 

After that, you will notice that the rand version in argo.lock has changed to 0.3.15 . If you want to use rand version 0.4.0 or any other 0.4.x version, then you will need to update Cargo.toml so that it looks like this:


 [dependencies] rand = "0.4.0" 

The next time you run the cargo build , Cargo will update the index of available crate and re-revise the dependencies. Cargo makes it very easy to connect third-party libraries, which contributes to code reuse ( code reuse ). It is easy
write new crate using existing building blocks.


Random number generation


Let's proceed to use rand , for this we update src / main.rs


 extern crate rand; use std::io; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); } 

We added extern crate rand; , which indicates rustc that you need to connect a third-party library. This is also similar to use rand , because now we have the opportunity to call functions from rand by writing rand:: . We also added use rand::Rng . Rng is a type that defines the methods that will be implemented by random number generators. This type must be in scope so that we can use its methods. We also added two lines in the middle. The rand::thread_rng function rand::thread_rng return a random number generator that is local to the current thread ( thread ), having been previously initialized ( seeded ) by the operating system. Next we call gen_range on the generator. This method is defined in the Rng , which we previously added to the scope by the use rand::Rng operator. gen_range takes two numbers and returns a random number between them. The range includes the lower limit and does not include the upper one, so we need to specify the numbers 1 and 101 in order to get a number from 1 to 100. To familiarize yourself with all the crate features, you need to read its documentation. Another possibility for Cargo is that you can "collect" the documentation by calling the cargo doc --open , after which the documentation will be opened in the browser (after assembly). If you are interested in other rand crate functionality, then select rand in the left pane. The second line we added prints the secret number. For now, let's leave it this way, it is convenient for checking the operation of the program, it will not be in the final version of the program. Run a couple of times:


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs Running `target/debug/guessing_game` Guess the number! The secret number is: 7 Please input your guess. 4 You guessed: 4 $ cargo run Running `target/debug/guessing_game` Guess the number! The secret number is: 83 Please input your guess. 5 You guessed: 5 

The program must each time display different random numbers from 1 to 100.


Compare the guess with the secret number


After we have generated a random number and got a guess from the user, we can compare them. Make changes to src / main.rs


 extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } 

The first unfamiliar element here is another use of the use statement, which introduces the type std::cmp::Ordering from std into the scope. Ordering is another enumeration, it is similar to Result , but has other options:



 match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } 

The cmp method compares two numbers, it can be called for any two entities that are comparable to each other. It gets a link to what you want to compare this element with: in this case, the guess and secret_number . cmp returns the Ordering variant (we previously added it to the scope by using the use operator). We also use match matching to decide, depending on the results of the comparison, how to proceed further. Match matching consists of branches ( arm s). A branch consists of a template and code that must be executed if the matching expression matches a branch template. Rust sequentially matches an expression with patterns in the match branches, and after a match is found, the code to the right of the matching pattern is executed. Let's look at an example of a possible interaction with the program. Let's say the user suggested the number 50 as a guess, and the secret (conceived) number is 38. When the code compares 50 and 38, the cmp method returns Ordering::Greater , because 50> 38. Ordering::Greater is the value that match . match looks at the pattern of the first branch, Ordering::Less , but the value of Ordering::Greater not comparable (in this case because it is not equal) to Ordering::Less , therefore ignores the code in this branch and proceeds to match the following patterns. The template for the next branch, Ordering::Greater comparable to Ordering::Greater (which we passed to match ). Too big! . match , . , :


 $ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) error[E0308]: mismatched types --> src/main.rs:23:21 | 23 | match guess.cmp(&secret_number) { | ^^^^^^^^^^^^^^ expected struct `std::string::String`, found integral variable | = note: expected type `&std::string::String` = note: found type `&{integer}` error: aborting due to previous error Could not compile `guessing_game`. 

, . Rust — , , , . let guess = String::new() , Rust , guess String , . , secret_number — . 1 100. :



 extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse() .expect("Please type a number!"); println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } 

:


 let guess: u32 = guess.trim().parse() .expect("Please type a number!"); 

guess . , ? , Rust ( shadow ), . , . ( shadowing ) ( identifier ), , guess_str guess . ( bind ) guess guess.trim().parse() . guess guess , . trim String . u32 , ENTER , ( read_line ). ENTER , . , 5 ENTER , guess 5\n . \n — , ENTER . trim \n , 5 . parse String , . , Rust , let guess: u32 . ( : ) guess , Rust , ( annotate ) . Rust , , u32 , 32- . . u32 guess guess secret_number , Rust secret_number u32 . ( u32 ). parse . , , A% , . - , , parse Result , , read_line . Result : expect . parse Err - , , expect , expect . parse , parse Ok , expect Ok . :


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs Running `target/guessing_game` Guess the number! The secret number is: 58 Please input your guess. 76 You guessed: 76 Too big! 

Fine! , , 76. , - :




loop . :


 extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse() .expect("Please type a number!"); println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } } 

, . :


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Running `target/guessing_game` Guess the number! The secret number is: 59 Please input your guess. 45 You guessed: 45 Too small! Please input your guess. 60 You guessed: 60 Too big! Please input your guess. 59 You guessed: 59 You win! Please input your guess. quit thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:785 note: Run with `RUST_BACKTRACE=1` for a backtrace. error: Process didn't exit successfully: `target/debug/guess` (exit code: 101) 

quit , , - . : , , .



, break :


 extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); println!("The secret number is: {}", secret_number); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = guess.trim().parse() .expect("Please type a number!"); println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } } 

break You win! , , , . , .



, , . , :


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

expect match . , . parse , Ok match , , Ok ( num ), parse Ok . guess . parse , Err , . Err Ok(num) match , Err(_) . _ , , Err(_) Err . match — continue , . :


 $ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Running `target/guessing_game` Guess the number! The secret number is: 61 Please input your guess. 10 You guessed: 10 Too small! Please input your guess. 99 You guessed: 99 Too big! Please input your guess. foo Please input your guess. 61 You guessed: 61 You win! 

Fine. .


 extern crate rand; use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("Guess the number!"); let secret_number = rand::thread_rng().gen_range(1, 101); loop { println!("Please input your guess."); let mut guess = String::new(); io::stdin().read_line(&mut guess) .expect("Failed to read line"); let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; println!("You guessed: {}", guess); match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } } 

Conclusion


Rust:



')

Source: https://habr.com/ru/post/349134/


All Articles