Rust Study

Spend time on studying RUST, stay hungry and stay foolish.

Never give up, keep climbing~

Reference

https://reberhardt.com/cs110l/spring-2020/

https://course.rs/about-book.html

https://practice.rs/why-exercise.html

https://practice.rs/why-exercise.html

https://www.runoob.com/rust/rust-tutorial.html

Programming a Guessing Game - The Rust Programming Language (rust-lang.org)

Install rust

fix success or fail

After installation, we run rustc -V and cargo -V and will see the details of version.

local documents

Run rustup doc.

When we don’t know how to use the functions, we can find them in API documents.

Build vscode

run rust code

I have installed and compiled the environment of the vscode with rust.

An example.

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

compile, build and run.

Using the command line and run main.exe, we can also get the output.

install plugins

Some recommended plugins.

  • Even Better TOML: support the complete .toml
  • Error Lens: emm, details of the errors. It will impact sagemath. so, I uninstall it
  • One Dark Pro: theme
  • CodeLLDB: Debugger

Cargo

compile project

Management about rust.

Run in command line, new the project.

Run cargo run, it will build and compile the project and print the output finally.

It defaults to debug mode. If we want to switch to release mode to optimize the project, we can run cargo run --release directly.

Use vscode directly, we also achieve the target.

  • cargo
    • cargo run mode: debug
    • cargo build mode: debug
    • cargo run --release mode: release
    • cargo build --release mode: release
    • cargo check check that the project code can be run if the size of project is too large.
  • vscode
    • Use according to what you need.

cargo documents

cargo.toml and cargo.lock are the core part of cargo. All things about cargo are about these two documents.

  • cargo.toml: description of project data, which is import for the rust project.
  • cargo.lock: detailed manifest of project dependencies, which is mainly depended on the rust project itself.

cargo.toml is like this. It first shows the details about the project, containing name/version/edition.

Second it shows what it depends on. There are three ways to introduce dependencies.

  • based on crates.io: specify version information
  • based on the url of the source code of the local repository
  • based on the absolute or relative path of the local repository

Three ways to import dependencies:

1
2
3
4
5
[dependencies]
rand = "0.3"
hammer = { version = "0.5.0"}
color = { git = "https://github.com/bjz/color-rs" }
geometry = { path = "crates/geometry" }

Hello World

RustProject/greeting

RustProject/advanced_code

test code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn greet_world() {
let southern_germany = "Grüß Gott!";
let chinese = "世界,你好";
let english = "World, hello";
let regions = [southern_germany, chinese, english];
for region in regions.iter() {
println!("{}", &region);
}
// or we can use this way to run:
// for region in regions(){
// println!("{}", &region);
}
}

fn main() {
greet_world();
}

How to run?

  • Vscode, and just run code.
  • Cmd, open a terminal and go to the root of world_hello project. Run cargo [project_name].

Look at the code carefully, we can get:

  • Rust natively supports UTF-8 encode strings.
  • println!. In Rust, this is the macro operator, and we can currently think of a macro as a special type of function.
  • We use {}, which can automatically recognize the type of the output data.
  • Rust’s collection types can’t loop directly and need to become iterators.

advanced code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
fn main() {
let penguin_data = "\
common name, length(cm)
Little penguin, 33
Yellow-eyed penguin, 65
Fiordland penguin, 60
Invalid, data
";

let records = penguin_data.lines();

for (i, record) in records.enumerate() {
if i == 0 || record.trim().len() == 0 {
continue;
}
// 声明一个 fields 变量,类型是 Vec
// Vec 是 vector 的缩写,是一个可伸缩的集合类型,可以认为是一个动态数组
// <_>表示 Vec 中的元素类型由编译器自行推断,在很多场景下,都会帮我们省却不少功夫
let fields: Vec<_> = record
.split(',')
.map(|field| field.trim())
.collect();

if cfg!(debug_assertions) {
//输入到标准错误输出
eprintln!("debug: {:?} -> {:?}", record, fields);
}

let name = fields[0];
// 1. 尝试把 fields[1] 的值转换为 f32 类型的浮点数,如果成功,则把 f32 值赋给 length 变量
//
// 2. if let 是一个匹配表达式,用来从=右边的结果中,匹配出 length 的值:
// 1)当=右边的表达式执行成功,则会返回一个 Ok(f32) 的类型,若失败,则会返回一个 Err(e) 类型,if let 的作用就是仅匹配 Ok 也就是成功的情况,如果是错误,就直接忽略
// 2)同时 if let 还会做一次解构匹配,通过 Ok(length) 去匹配右边的 Ok(f32),最终把相应的 f32 值赋给 length
//
// 3. 当然你也可以忽略成功的情况,用 if let Err(e) = fields[1].parse::<f32>() {...}匹配出错误,然后打印出来
if let Ok(length) = fields[1].parse::<f32>() {
//输出到标准形式
println!("{}, {}cm", name, length)
}

}
}

debug

terminal

1
cargo run

cmd

release

terminal

1
cargo run --release

cmd

understand code

  • Control flow: for go with continue
  • Method syntax: Since Rust has no inheritance, Rust is not an object-oriented language in the traditional sense, but it steals methods from the OO language using record.trim(), record.split(','), etc.
  • Lambda function: Functions can be taken as parameters or as return values.

Guess game

RustProject/guessing_game

accept the user’s input

1
2
3
4
5
6
7
8
9
10
11
12
use std::io; 
fn main() {
println!("Guessing the number!");
println!("Please input your guess:");

let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.except("Failed to read line");

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

go over it line by line

1
use std::io; 

bring the io library which comes from the standard library (std)

1
fn main(){}

fn means a new function

1
2
println!("Guessing the number!");
println!("Please input your guess:");
  • state what the game is

  • request input from the user

1
let mut guess = String::new();

let statement to create a variable to store the user’s input, to remember that once given it will not change, for example:

1
2
let apples = 5;
// a new variable named apples and binds to the value 5

Thus, we add mut before the variable name.

1
2
3
//example
let apples = 5; //immutable
let mut bananas = 5; //mutable

the :: in ::new line indicates that new is an associated function of the String type.

let mut guess = String::new();means that it created a mutable variable that is currently bound to a new, empty instance of a String.

1
2
io::stdin()
.read_line(&mut guess)

call stdin function from io module, which allows us to handle user input

.read_line(&mut guess) call the read_line method on the standard input to get input from the user.

pass &mut guess to read_line to tell string to store the user’s input

& indicates that this argument is a reference, no need to copy that data into memory multiple times

1
.expect("Failed to read line");

return a Result type to encode error-handling information

Result‘s variants are Ok and Err.

If we don’t call expect, the program will compile, but it will get a warning.

The right way to suppress the warning is to actually write error-handling code, but in our case we just want to crash this program when a problem occurs, so we can use expect.

PAY ATTENTION ! Expect NOT Except !!!

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

The line prints the string that now contains the user’s input.

generate a random number

Aim to generate a secret number that we will try to guess.

The random number between 1 and 100.

The rand crate is a library crate, which contains code that is intended to be used in other programs and can’t be executed on its own.

crate is a term in Rust, meaning a stand-alone library or project

library crate which means the project contains library code

So we should import it in Cargo.toml which include the rand crate as a dependency.

1
2
3
[dependencies]
rand = "0.8.5"
// rand = "^0.8.5" at least 0.8.5 but below 0.9.0

contains below:

  • crate name
  • crate version

run cargo build after adding the rand crate as a dependency

After updating the registry, Cargo checks the [dependencies] section and downloads any crates listed that aren’t already downloaded. In this case, although we only listed rand as a dependency, Cargo also grabbed other crates that rand depends on to work. After downloading the crates, Rust compiles them and then compiles the project with the dependencies available.

add code to generate a random number:

1
2
3
4
use rand::Rng;

let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");

go over it line by line

First, add the line use rand::Rng;. Rng trait defines methods that random generators implement, and the trait must be in scope for us to use those methods.

Second, we call rand::thread_rng function that gives us the random generator, and it is local to the current thread of execution and is seeded by the operating system.

Third, gen_rangeis a function that define the random number range, which we brought into scope with the rand::Rng; statement.

The usage is that start..=end, is inclusive on the lower and upper bounds. For example 1..=100 means request a number between 1 and 100.

You won’t just know which traits to use and which methods and functions to call from a crate, so each crate has documentation with instructions for using it.

ensure reproducible build with the cargo.lock file

Rust creates the Cargo.lock file the first time when running cargo build.

The Cargo.lock file is important for reproducible builds, and it’s often checked into source control with the rest of the code in your project.

update a crat to get a new version

1
cargo update

and the dependencies will change.

The next time you run cargo build, Cargo will update the registry of crates available and reevaluate your rand requirements according to the new version you have specified.

Cargo makes it very easy to reuse libraries, so Rustaceans are able to write smaller projects that are assembled from a number of packages.

Rustaceans means users or developers who use the Rust

at this time, the complete code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::io; //bring the io library which comes from the standard library (std)
use rand::Rng;

fn main() { //fn means a new function
println!("Guessing the number!"); //state what the game is

let secret_number = rand::thread_rng().gen_range(1..=100);

println!("The secret number is: {secret_number}");

println!("Please input your guess:"); //request input from the user

let mut guess = String::new(); //'let' statement to create a variable to store the user's input, to rmember that once given it will not change
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line"); // not except

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

when we run cargo run, we will see:

However it won’t achieve the aim to generate a secret number and compare it to the guessing number.

compare the guess to the secret number

Step1

make a comparison

1
2
3
4
5
6
7
8
9
10
use std::cmp::Ordering

// based on the original foundation
let guess: u32 = guess.trim().parse().expect("Please type a number!");

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

However it return a number comparison and break.

Step2

turn into a loop

Notice the numbers’ usage and the order of using these different numbers.

We ought to generate different guess to solve out the secret_number which is generated by the system.

So secret_number is above loop.

1
2
3
4
5
6
7
8
9
10
11
let secret_number = rand::thread_rng().gen_range(1..=100);

loop{
let mut guess = String::new();
let guess: u32 = guess.trim().parse().expect("Please type a number!");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("YOU WIN!"),
}
}

Step3

equal then break

1
2
3
4
5
6
7
8
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("YOU WIN!");
break;
}
}

complete code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use std::io;
use rand::Rng;
use std::cmp::Ordering;

fn main(){
// generte a random number
let secret_number = rand::thread_rng().gen_range(1..=100);

// guess number, need to define the type is real a number
// the precess of guessing is changeable which we should let it in a loop
loop {
println!("Please input your guess!");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Fail to read line!");
let guess: u32 = guess.trim().parse().expect("Please type a number!");

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

// compare the two numbers
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("YOU WIN!");
break;
}
}
}
}

run cargo run:

handle invalid input

To further refine the game’s behavior, rather than crashing the program when the user inputs a non-number, let’s make the game ignore a non-number so the user can continue guessing. We can do that by altering the line where guess is converted from a String to a u32, as shown in Listing 2-5.

1
2
3
4
5
//let guess: u32 = guess.trim().parse().expect("Please type a number!");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};

go over it line by line

Switch an expect call to a match expression to move from crashing on an error to handle the error.

The parse returns a Result type and Result is an enum that has the variants Ok and Err.

We use a match expression here, same as the Ordering result if the cmp method.

  • If parse is able to turn the string into a number, return Ok contains resultant number. The number will end up right where we want it in the new guess variable we’re creating.
  • If not, it will return an Err value, we just continue and return to loop to ask for another guess.

Finally ,the program ignores all errors that parse might encounter.

Common programming concepts

variables and mutability

1
2
3
4
5
6
7
8
9
10
11
12
13
// fn main() {
// let x = 5;
// println!("The value of x is: {x}");
// x = 6;
// println!("The value of x is: {x}");
// }

fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
-------------THE END-------------