Resources for this can be found from here.
// main.rs fn main() { println!("Hello, world!"); }
Note: println!
calls a Rust macro. If it called a function instead, it would be entered as println (without the !). For now, you just need to know that using a ! means that you’re calling a macro instead of a normal function.
Cargo is Rust’s build system and package manager. Most Rustaceans use this tool to manage their Rust projects because Cargo handles a lot of tasks for you, such as building your code, downloading the libraries your code depends on, and building those libraries. (We call libraries your code needs dependencies.)
$ cargo new hello_cargo $ cd hello_cargo
The first command creates a new directory called hello_cargo. We’ve named our project hello_cargo, and Cargo creates its files in a directory of the same name.
Go into the hello_cargo directory and list the files. You’ll see that Cargo has generated two files and one directory for us: a Cargo.toml file and a src directory with a main.rs file inside. It has also initialized a new Git repository along with a .gitignore file.
This will be the cargo.toml
file.
[package] name = "hello_cargo" version = "0.1.0" authors = ["Your Name <you@example.com>"] edition = "2018" [dependencies]
$ cargo build # This command creates an executable file in target/debug/hello_cargo (or target\debug\hello_cargo.exe on Windows) rather than in your current directory. You can run the executable with this command: $ ./target/debug/hello_cargo > Hello, world! # We can also run with cargo run $ cargo run # Cargo also provides a command called cargo check. This command quickly checks your code to make sure it compiles but doesn’t produce an executable $ cargo check
$ cargo build --release
This command will create an executable in target/release instead of target/debug. The optimizations make your Rust code run faster, but turning them on lengthens the time it takes for your program to compile. This is why there are two different profiles: one for development, when you want to rebuild quickly and often, and another for building the final program you’ll give to a user that won’t be rebuilt repeatedly and that will run as fast as possible. If you’re benchmarking your code’s running time, be sure to run cargo build --release
and benchmark with the executable in target/release.
cargo new guessing_game
// guessing_game/src/main.rs 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); }
This code contains a lot of information, so let’s go over it line by line. To obtain user input and then print the result as output, we need to bring the io (input/output) library into scope. The io library comes from the standard library (which is known as std):
use std::io;
By default, Rust brings only a few types into the scope of every program in the prelude. If a type you want to use isn’t in the prelude, you have to bring that type into scope explicitly with a use statement. Using the std::io
library provides you with a number of useful features, including the ability to accept user input.
The following example shows how to use mut
before the variable name to make a variable mutable:
let foo = 5; // immutable let mut bar = 5; // mutable
The ::
syntax in the ::new
line indicates that new is an associated function of the String
type. An associated function is implemented on a type, in this case String, rather than on a particular instance of a String. Some languages call this a static method.
This new function creates a new, empty string. You’ll find a new function on many types, because it’s a common name for a function that makes a new value of some kind.
To summarize, the let mut guess = String::new();
line has created a mutable variable that is currently bound to a new, empty instance of a String.
Recall that we included the input/output functionality from the standard library with use std::io
; on the first line of the program. Now we’ll call an associated function, stdin
, on io
:
io::stdin().read_line(&mut guess) .expect("Failed to read line");
If we hadn’t listed the use std::io
line at the beginning of the program, we could have written this function call as std::io::stdin
. The stdin function returns an instance of std::io::Stdin
, which is a type that represents a handle to the standard input for your terminal.
The &
indicates that this argument is a reference, which gives you a way to let multiple parts of your code access one piece of data without needing to copy that data into memory multiple times. References are a complex feature, and one of Rust’s major advantages is how safe and easy it is to use references.
As mentioned earlier, read_line
puts what the user types into the string we’re passing it, but it also returns a value—in this case, an io::Result
. Rust has a number of types named Result
in its standard library: a generic Result
as well as specific versions for submodules, such as io::Result
.
The Result
types are enumerations, often referred to as enums
. An enumeration is a type that can have a fixed set of values, and those values are called the enum’s variants.
For Result, the variants are Ok
or Err
. The Ok
variant indicates the operation was successful, and inside Ok
is the successfully generated value. The Err
variant means the operation failed, and Err
contains information about how or why the operation failed.
The set of curly brackets, {}
, is a placeholder: think of {}
as little crab pincers that hold a value in place. You can print more than one value using curly brackets: the first set of curly brackets holds the first value listed after the format string, the second set holds the second value, and so on. Printing multiple values in one call to println! would look like this:
fn main() { let x = 5; let y = 10; println!("x = {} and y = {}", x, y); }
This code would print x = 5 and y = 10.
In this case, we would have to update our Cargo.toml
file to include the rand
crate
[dependencies] rand = "0.4.0"
cargo build
to install the dependency, but running cargo update
would also update the version for the latest minor version.
In use, we can now random generate a number:
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 can now use another standard library to help give more information on the accuracy of the guess:
use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { // --snip-- 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!"), } }
Note here that we are using some standard string methods to trim, parse and expect a number type for the guess
variable.
The parse
method on strings parses a string into some kind of number. Because this method can parse a variety of number types, we need to tell Rust the exact number type we want by using let guess: u32
. The colon (:) after guess tells Rust we’ll annotate the variable’s type. Rust has a few built-in number types; the u32 seen here is an unsigned, 32-bit integer. It’s a good default choice for a small positive number. You’ll learn about other number types in Chapter 3. Additionally, the u32
annotation in this example program and the comparison with secret_number
means that Rust will infer that secret_number
should be a u32
as well. So now the comparison will be between two values of the same type!
The call to parse could easily cause an error. If, for example, the string contained A👍%
, there would be no way to convert that to a number. Because it might fail, the parse method returns a Result type, much as the read_line method does (discussed earlier in "Handling Potential Failure with the Result Type"). We’ll treat this Result the same way by using the expect method again. If parse returns an Err Result variant because it couldn’t create a number from the string, the expect call will crash the game and print the message we give it. If parse can successfully convert the string to a number, it will return the Ok variant of Result, and expect will return the number that we want from the Ok value.
// --snip-- println!("The secret number is: {}", secret_number); loop { println!("Please input your guess."); // --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => println!("You win!"), } } }
This is suboptimal as the only way to exit at the moment is to pass a string that cannot be passed to cause an error.
What we can do instead is update this is break the loop at particular parts:
// --snip-- match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } } }
We could also now update the parse
method to handle and continue on errors now that we are looping:
// --snip-- 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); // --snip--
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; } } } }