1. Getting Started

Installation

直接看官方文档

https://doc.rust-lang.org/book/ch01-01-installation.html

Hello, World!

在windows和Linux上分别创建并运行hello world项目熟悉RUST语言特性

两者共用源代码 hello_world.rs

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

Windows

编译

1
rustc hello_world.rs

运行

1
.\main.exe

Linux

编译

1
rustc hello_world.rs

运行

1
./main

总结

  • 在RUST中创建函数

    1
    2
    3
    fn main() {

    }
    • The main function is special: it is always the first code that runs in every executable Rust program
  • 功能函数

    1
    println!("Hello, world!");
    • rust使用四个空格来替代Tab
    • 这里的println时调用了一个宏(macros),当要调用println函数时,不需要添加感叹号
    • 语句结束使用;
  • rustc

    相当于rust的gcc编译器

Cargo

相当于是rust的pip,是一个包管理工具

使用Cargo创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
cargo new hello_prog
cd hello_cargo
tree
.
├── Getting Started.md
├── hello_prog
│   ├── Cargo.toml
│   └── src
│   └── main.rs
├── hello_world
├── hello_world.exe
├── hello_world.pdb
└── hello_world.rs

使用

1
cargo new --vcs=git.

来避免创建git的gitignore文件

Cargo.toml

1
2
3
4
5
6
7
8
[package]
name = "hello_prog"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

The first line, [package], is a section heading that indicates that the following statements are configuring a package. As we add more information to this file, we’ll add other sections.

The next three lines set the configuration information Cargo needs to compile your program: the name, the version, and the edition of Rust to use. We’ll talk about the edition key in Appendix E.

The last line, [dependencies], is the start of a section for you to list any of your project’s dependencies. In Rust, packages of code are referred to as crates. We won’t need any other crates for this project, but we will in the first project in Chapter 2, so we’ll use this dependencies section then.

编译运行

在文件夹中

1
cargo build

运行

1
./target/debug/hello_prog

编译并运行

1
cargo run

编译测试

1
cargo check

Summary

You’re already off to a great start on your Rust journey! In this chapter, you’ve learned how to:

  • Install the latest stable version of Rust using rustup
  • Update to a newer Rust version
  • Open locally installed documentation
  • Write and run a “Hello, world!” program using rustc directly
  • Create and run a new project using the conventions of Cargo

This is a great time to build a more substantial program to get used to reading and writing Rust code. So, in Chapter 2, we’ll build a guessing game program. If you would rather start by learning how common programming concepts work in Rust, see Chapter 3 and then return to Chapter 2.

2-Programming a Guessing Game

You’ll learn about let, match, methods, associated functions, using external crates, and more! The following chapters will explore these ideas in more detail

创建项目

1
cargo new guessing_game

处理猜测

让用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::io;	//python库

fn main() {
println!("Guess a Number");
print!("Please input your guess> ");

let mut guess = String::new();

io::stdin() //调用io中的stdin方法
.read_line(&mut guess) //python库中的功能
.expect("Failed to readline"); //失败预处理
println!("You guessed: {}", guess);
}
  • let statement, which is used to create a variable.
  • mut变量类型不可变
  • 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.
  • read_line函数返回一个io::Result值,值是枚举的变量:OkErr

产生随机数

在配置文件中添加

1
rand = "0.8.3"

The answer to this problem is the Cargo.lock file, which was created the first time you ran cargo build and is now in your guessing_game directory. When you build a project for the first time, Cargo figures out all the versions of the dependencies that fit the criteria and then writes them to the Cargo.lock file. When you build your project in the future, Cargo will see that the Cargo.lock file exists and use the versions specified there rather than doing all the work of figuring out versions again. This lets you have a reproducible build automatically.

your project will remain at 0.8.3 until you explicitly upgrade, thanks to the Cargo.lock file.

可以使用

1
cargo update

进行库更新

现在的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::io;	//python库
use rand::Rng;

fn main() {
//1-产生随机数
let secret_num = rand::thread_rng().gen_range(1..101);
println!("Random number> {}", secret_num);

//2-输入
println!("Guess a Number");
print!("Please input your guess> ");

let mut guess = String::new();

io::stdin() //调用io中的stdin方法
.read_line(&mut guess) //python库中的功能
.expect("Failed to readline"); //失败预处理
println!("You guessed: {}", guess);
}

you can run the cargo doc --open command, which will build documentation provided by all of your dependencies locally and open it in your browser.

比较数字

we want to convert the String the program reads as input into a real number type so we can compare it numerically to the secret number. We can do that by adding another line to the main function body:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!"),
}

创造循环体

1
2
3
loop{

}

退出循环

1
2
3
Ordering::xxx => {
break;
}

非法输入处理

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
use std::io;	//类似python库
use rand::Rng; //随机数库
use std::cmp::Ordering; //比较功能库

fn main() {
//1-产生随机数
let secret_num = rand::thread_rng().gen_range(1..101);
println!("Random number> {}", secret_num);

//4-循环
loop {
//2-1输入
println!("Please input your guess> ");

let mut guess = String::new();

io::stdin() //调用io中的stdin方法
.read_line(&mut guess) //python库中的功能
.expect("Failed to readline"); //失败预处理

//2-2转换类型
//5-处理非法输入
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input");
continue;
},
};
println!("You guessed: {}", guess);

//3-比较数字大小
match guess.cmp(&secret_num) {
Ordering::Less => println!("Too small"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win");
break;
}
}
}
}

总结

At this point, you’ve successfully built the guessing game. Congratulations!

This project was a hands-on way to introduce you to many new Rust concepts: let, match, functions, the use of external crates, and more. In the next few chapters, you’ll learn about these concepts in more detail. Chapter 3 covers concepts that most programming languages have, such as variables, data types, and functions, and shows how to use them in Rust. Chapter 4 explores ownership, a feature that makes Rust different from other languages. Chapter 5 discusses structs and method syntax, and Chapter 6 explains how enums work.

3-Common Programming Concepts

This chapter covers concepts that appear in almost every programming language and how they work in Rust. Many programming languages have much in common at their core. None of the concepts presented in this chapter are unique to Rust, but we’ll discuss them in the context of Rust and explain the conventions around using these concepts.

Specifically, you’ll learn about variables, basic types, functions, comments, and control flow. These foundations will be in every Rust program, and learning them early will give you a strong core to start from.

RUST关键字

https://doc.rust-lang.org/book/appendix-01-keywords.html

变量和可变量

  • 变量类型默认是不可变的

src/main.rs

1
2
3
4
5
6
7
fn main() {
let x= 5;
println!("the value of x is {}", x);

x=6;
println!("the value of x is {}", x);
}

使用cargo run会报错

1
2
5 |     x=6;
| ^^^ cannot assign twice to immutable variable

使用mut来创造可变变量

1
2
3
4
5
6
fn main() {
let mut x = 5;
println!("the value of x is {}", x);
x = 6;
println!("the value of x is {}", x);
}
  • 变量和常量对比

    1. 常量永远为常,一直不可变
    2. 定义常量使用const而不是let,同时指明变量类型
    3. 常量定义尽量使用大写
    4. https://doc.rust-lang.org/reference/const_eval.html
  • 隐式变量

    you can declare a new variable with the same name as a previous variable,Rustaceans say that the first variable is shadowed by the second, which means that the second variable’s value is what the program sees when the variable is used.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //变量的隐式赋值
    fn shadow_value() {
    let x = 5;
    let x = x + 1;
    {
    let x = x * 2;
    println!("The value of x in the inner scope is {}", x);
    }
    println!("The value of x is {}",x);
    }

    输出

    1
    2
    The value of x in the inner scope is 12
    The value of x is 6

    This program first binds x to a value of 5. Then it shadows x by repeating let x =, taking the original value and adding 1 so the value of x is then 6.

    Then, within an inner scope, the third let statement also shadows x, multiplying the previous value by 2 to give x a value of 12.

    When that scope is over, the inner shadowing ends and x returns to being 6

    By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have been completed.

数据类型

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters. You may recognize these from other programming languages.

基础变量

Integer Types

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize
Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte (u8 only) b'A'

integer types default to i32. The primary situation in which you’d use isize or usize is when indexing some sort of collection.

rust能在编译中整数溢出发生时检测到(Panic)

Floating-Point Types

1
2
3
4
fn floating_tpyes() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}

数字基本加减法

1
2
3
4
5
6
7
8
//基础的数学运算
fn basic_math() {
let sum = 5 + 10;
let difference = 95 - 1;
let product = 4 * 30;
let floored = 2 / 3;
let reminder = 43 % 5;
}

The Boolean Type

1
2
3
4
5
fn boolean_test() {
let t = true;

let f: bool = false; // with explicit type annotation
}

The Character Type

So far we’ve worked only with numbers, but Rust supports letters too. Rust’s char type is the language’s most primitive alphabetic type, and the following code shows one way to use it. (Note that char literals are specified with single quotes, as opposed to string literals, which use double quotes.)

Filename: src/main.rs

1
2
3
4
5
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
}

符合变量

元组 Tuple

1
let tup: (i32, f64, u8) = (500, 6.4, 1);

你甚至可以

1
2
3
4
5
fn nice_tulpe() {
let tup = (500,6.4,1);
let (x,y,z) = tup;
println!("The value of y is {}",y);
}

使用.运算直接进行下标访问

1
2
3
4
5
6
fn nice_tulpe() {
let tup = (500,6.4,1);
let (x,y,z) = tup;
println!("The value of y is {}",y);
println!("using . > {}",tup.0);
}

数组类型 Array Type

1
2
3
let a = [1,2,3,4,5];
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5];//let a = [3,3,3,3,3];
  • 数组访问

    1
    2
    3
    4
    5
    6
    fn nice_array() {
    let a = [1, 2, 3, 4, 5];
    let first = a[0];
    let second = a[1];
    println!("fisrt> {}\nsencond> {}",first,second);
    }
  • 非法数组访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fn bad_array() {
    let a = [1,2,3,4,5];
    println!("enter the index");

    let mut idx = String::new();
    io::stdin()
    .read_line(&mut idx)
    .expect("failed to readline");

    let idx: usize = idx
    .trim()
    .parse()
    .expect("Index enter was not a number");

    let elen = a[idx];
    println!(
    "The value in a[{}] is {}",
    idx,a[idx]
    );
    }

    输入超出范围时rust报错

    1
    2
    3
    4
    enter the index
    10
    thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:71:16
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    If the index is greater than or equal to the length, Rust will panic

函数

不带返回值

1
2
3
fn another_function(x: i32) {
println!("The value of x is: {}", x);
}

带返回值

1
2
3
fn five() -> i32 {
5
}

函数返回最后一个出现的所要求的类型的数据,同样可以使用return 返回值

函数表达式不等同于函数结构体,不能是使用return 关键字

逻辑判断与循环

if Expressions

An if expression allows you to branch your code depending on conditions. You provide a condition and then state, “If this condition is met, run this block of code. If the condition is not met, do not run this block of code.”

Create a new project called branches in your projects directory to explore the if expression. In the src/main.rs file, input the following:

Filename: src/main.rs

1
2
3
4
5
6
7
8
9
fn main() {
let number = 3;

if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}

可以利用if条件进行赋值(语法糖)//注意两个值的类型要匹配

1
2
3
4
5
6
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };

println!("The value of number is: {}", number);
}

Repetition with Loops

Rust has three kinds of loops: loop, while, and for. Let’s try each one.

loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {}", count);
let mut remaining = 10;

loop {
println!("remaining = {}", remaining);
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}

count += 1;
}
println!("End count = {}", count);
}

同样使用break退出

Returning Values from Loops

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut counter = 0;

let result = loop {
counter += 1;

if counter == 10 {
break counter * 2;
}
};

println!("The result is {}", result);
}

break返回后面值

Summary

You made it! That was a sizable chapter: you learned about variables, scalar and compound data types, functions, comments, if expressions, and loops! If you want to practice with the concepts discussed in this chapter, try building programs to do the following:

  • Convert temperatures between Fahrenheit and Celsius.
  • Generate the nth Fibonacci number.
  • Print the lyrics to the Christmas carol “The Twelve Days of Christmas,” taking advantage of the repetition in the song.

When you’re ready to move on, we’ll talk about a concept in Rust that doesn’t commonly exist in other programming languages: ownership.

4-Understanding Ownership

Feature represent

Rust’s central feature is ownership

All programs have to manage the way they use a computer’s memory while running. Some languages have garbage collection that constantly looks for no longer used memory as the program runs; in other languages, the programmer must explicitly allocate and free the memory. Rust uses a third approach: memory is managed through a system of ownership with a set of rules that the compiler checks at compile time. None of the ownership features slow down your program while it’s running.

……

Keeping track of what parts of code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses.

Ownership Rules

  • Each value in Rust has a variable that’s called its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

Variable Scope

  • 在函数表达式中的值,在函数表达完成后自动删除

使用string类型例子,为了更好的存储和使用字符串值,我们可以使用String类型

1
let s = String::from("hello");

其中::参考了c++命名空间

1
2
3
4
5
fn main() {
let mut s = String::from("hello");
s.push_str(",Rust");
println!("{}",s);
}

String中为了支持类型可变、长度可变,在编译时长度不可变

  • 在运行时内存必须由内存分配器分配
  • 结束使用时要用某种方式告诉内存

We need to pair exactly one allocate with exactly one free.

Rust使用了一种和其他语言不一样的方式,当变量超出其定义范围时GC自动回收这块内存

如在4-1中所讲的那样

1
2
3
4
5
6
let s = String::from("rust");
{
let s = String::from("hello");
println!("{}",s);
}
println!("{}",s);

When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.

Note: In C++, this pattern of deallocating resources at the end of an item’s lifetime is sometimes called Resource Acquisition Is Initialization (RAII). The drop function in Rust will be familiar to you if you’ve used RAII patterns.

1
2
3
4
let s1 = String::from("hello");
let s2 = s1;
println!("{}",s1);
println!("{}",s2);
1
2
3
4
5
6
7
8
9
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:18:19
|
16 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
17 | let s2 = s1;
| -- value moved here
18 | println!("{}",s1);
| ^^ value borrowed here after move

在rust中,一个string结构体为

在复制时,仅仅复制结构体,而不复制内存中的数据

但是rust和其它语言的浅拷贝不同,它是将s1移动到了s2,相当于

1
2
3
4
let s = String::from("hello");
{
let s = String::from("hello");
}

这种类似的移动,所以真实情况看上去啊像是

这样的,所以在使用时,我们只能使用s2

同时如果我们想要做到deep cpoy,我们可以使用clone功能来实现

1
2
3
4
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}",s1);
println!("s2 = {}",s2);

Rust won’t let us annotate a type with the Copy trait if the type, or any of its parts, has implemented the Drop trait.

Here are some of the types that implement Copy:

  • All the integer types, such as u32.
  • The Boolean type, bool, with values true and false.
  • All the floating point types, such as f64.
  • The character type, char.
  • Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

Ownership and Functions

The book直接给出了一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
//5-ownership and functions
let s = String::from("hello");
takes_ownership(s);
let x = 5;
makes_copy(x);

}
//5-ownership and functions
fn takes_ownership(some_string: String) {
println!("{}",some_string);
}

//5-ownership and functions
fn makes_copy(some_integar: i32) {
println!("{}",some_integar);
}

我们在takes_ownership中使用了在堆上的变量s后,会调用drop这个功能释放s,是的后续不能再次使用

而我们的makes_copy在栈上面所以能再次使用

Return Values and Scope

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
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1

let s2 = String::from("hello"); // s2 comes into scope

let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it

let some_string = String::from("yours"); // some_string comes into scope

some_string // some_string is returned and
// moves out to the calling
// function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope

a_string // a_string is returned and moves out to the calling function
}

The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the heap goes out of scope, the value will be cleaned up by drop unless the data has been moved to be owned by another variable.

What if we want to let a function use a value but not take ownership?

使用元组传递结果

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let s1 = String::from("hello");

let (s2, len) = calculate_length(s1);

println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String

(s, length)
}

But this is too much ceremony and a lot of work for a concept that should be common. Luckily for us, Rust has a feature for this concept, called references.

References and Borrowing

可以借鉴c++中的例子,rust中,同样使用&var来表示对参数的引用,the book同样给出了一个例子

1
2
3
4
5
6
7
8
9
fn main() {
let s1 = String::from("hello");
let len: usize = calculate_lenth(&s1);
println!("{} lenth is {}",s1,len);
}

fn calculate_lenth(s: &String) -> usize {
s.len()
}

同样的,rust存在反引用*

1
2
3
4
5
6
fn calculate_lenth(s: &String) -> usize {
println!("{}",s.capacity());
// println!("{}",*s.capacity()); false
println!("{}",s);
s.len()
}

We call the action of creating a reference borrowing

错误示例代码

1
2
3
4
5
6
7
8
9
fn main() {
let s = String::from("hello");

change(&s);
}

fn change(some_string: &String) {
some_string.push_str(", world");
}

原因:Just as variables are immutable by default, so are references. We’re not allowed to modify something we have a reference to.

Mutable References

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
//2-mut reference
let mut s = String::from("hello");

change(&mut s);
println!("{}",s);
}

//2-mut reference
fn change(some_string: &mut String) {
some_string.push_str(", world");
}

可变引用有限制:一个可变变量只能被一个可变引用占有

benefit: The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion. It’s something that new Rustaceans struggle with, because most languages let you mutate whenever you’d like.

几种错误可变引用示例

1
2
3
4
5
6
7
8
fn main() {
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

println!("{}, {}", r1, r2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership`

To learn more, run the command again with --verbose.

修正:

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello");

{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.

let r2 = &mut s;
}

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM

println!("{}, {}, and {}", r1, r2, r3);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership`

To learn more, run the command again with --verbose.

修正

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point

let r3 = &mut s; // no problem
println!("{}", r3);
}

Dangling References

rust引起的指针悬空操作会被编译器察觉

1
2
3
4
5
6
7
8
9
fn main() {
let reference_to_nothing = dangle();
}

fn dangle() -> &String {
let s = String::from("hello");

&s
}

The Rules of References

Let’s recap what we’ve discussed about references:

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

The Slice Type

Another data type that does not have ownership is the slice. Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.

例子:使用引用来判断字符串是否为单词,扫描字符串,发现有空格则返回空格所在位置,没有则返回这个单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
println!("{}",word);
s.clear();
}

fn first_word(s: &String) -> usize{
let bytes = s.as_bytes();//将str切换为bytes数组

//enumerate wraps the result of iter and returns each element as part of a tuple instead.
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}

为了对该单词进行分段,我们定义一个新的函数

1
fn second_word(s: &String) -> (usize, usize) {

String Slices

A string slice is a reference to part of a String, and it looks like this:

1
2
3
4
5
6
let mut s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];
println!("{}",hello);
println!("{}",world);

在过程中的两个变量为

Note: String slice range indices must occur at valid UTF-8 character boundaries. If you attempt to create a string slice in the middle of a multibyte character, your program will exit with an error. For the purposes of introducing string slices, we are assuming ASCII only in this section; a more thorough discussion of UTF-8 handling is in the “Storing UTF-8 Encoded Text with Strings” section of Chapter 8.

所以我们可以重新写上个例子为

1
2
3
4
5
6
7
8
9
10
11
fn second_word(s: &String) -> &str {
let bytes = s.as_bytes();//将str切换为bytes数组

//enumerate wraps the result of iter and returns each element as part of a tuple instead.
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}

错误调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}

&s[..]
}

fn main() {
let mut s = String::from("hello world");

let word = first_word(&s);

s.clear(); // error!

println!("the first word is: {}", word);
}

Recall from the borrowing rules that if we have an immutable reference to something, we cannot also take a mutable reference.

String Slices as Parameters

Other Slices

String slices, as you might imagine, are specific to strings. But there’s a more general slice type, too. Consider this array:

1
let a = [1, 2, 3, 4, 5];

Just as we might want to refer to a part of a string, we might want to refer to part of an array. We’d do so like this:

1
2
3
4
5
let a = [1, 2, 3, 4, 5];

let slice = &a[1..3];

assert_eq!(slice, &[2, 3]);

This slice has the type &[i32]. It works the same way as string slices do, by storing a reference to the first element and a length. You’ll use this kind of slice for all sorts of other collections. We’ll discuss these collections in detail when we talk about vectors in Chapter 8.

Summary

The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time. The Rust language gives you control over your memory usage in the same way as other systems programming languages, but having the owner of data automatically clean up that data when the owner goes out of scope means you don’t have to write and debug extra code to get this control.

Ownership affects how lots of other parts of Rust work, so we’ll talk about these concepts further throughout the rest of the book. Let’s move on to Chapter 5 and look at grouping pieces of data together in a struct.

5-Using Structs to Structure Related Data

In this chapter, we’ll compare and contrast tuples with structs. We’ll demonstrate how to define and instantiate structs. We’ll discuss how to define associated functions, especially the kind of associated functions called methods, to specify behavior associated with a struct type.

Defining and Instantiating Structs

To define a struct, we enter the keyword struct and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields. For example, Listing 5-1 shows a struct that stores information about a user account.

1
2
3
4
5
6
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

实例化结构体

1
2
3
4
5
6
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

To get a specific value from a struct, we can use dot notation

1
2
3
4
5
6
7
8
9
//使用mutable结构体
let mut user2 = User {
active: true,
username: String::from("admin"),
sign_in_count: 0,
email: String::from("admin@covteam.club"),
};

user2.email=String::from("admin@covteam.cn");

使用函数实例化结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
let user3 = build_user(String::from("test@covteam.com"), String::from("username"));
println!("user3 email> {}",user3.email);
println!("user3 username> {}",user3.username);
}

fn build_user(email: String, username: String) -> User{
User{
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}

可以省略的填入参数实例化结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}

fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}

Creating Instances From Other Instances With Struct Update Syntax

1
2
3
4
5
6
let user4 = User {
active: user1.active,
username: user2.username,
email: String::from("some_one@qq.com"),
sign_in_count: user3.sign_in_count,
};

The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.

1
2
3
4
let user5 = User {
email: String::from("joe1sn@qq.com"),
..user1
};

但是如果copy的是可变变量(如user2),会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
error[E0382]: use of moved value: `user2.username`
--> src/main.rs:38:17
|
33 | username: user2.username,
| -------------- value moved here
...
38 | let user5 = User {
| _________________^
39 | | email: String::from("joe1sn@qq.com"),
40 | | ..user2
41 | | };
| |_____^ value used here after move
|
= note: move occurs because `user2.username` has type `String`, which does
not implement the `Copy` trait

其中可以这样理解

**Note that the struct update syntax is like assignment with = because it moves the data, just as we saw in the “Ways Variables and Data Interact: Move” section. **

Using Tuple Structs without Named Fields to Create Different Types

参考go的结构体构造其实也是如此

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Unit-Like Structs Without Any Fields

应用方式:unit-like structs can be useful in situations in which you need to implement a trait on some type but don’t have any data that you want to store in the type itself.

1
2
3
4
5
fn main() {
struct AlwaysEqual;

let subject = AlwaysEqual;
}

without any curly brackets or parentheses. Imagine we’ll be implementing behavior for this type that every instance is always equal to every instance of every other type

You’ll see in Chapter 10 how to define traits and implement them on any type, including unit-like structs.

In the User struct definition in Listing 5-1, we used the owned String type rather than the &str string slice type. This is a deliberate choice because we want instances of this struct to own all of its data and for that data to be valid for as long as the entire struct is valid.

It’s possible for structs to store references to data owned by something else, but to do so requires the use of lifetimes, a Rust feature that we’ll discuss in Chapter 10. Lifetimes ensure that the data referenced by a struct is valid for as long as the struct is. Let’s say you try to store a reference in a struct without specifying lifetimes, like this, which won’t work:

Filename: src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}

fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}

The compiler will complain that it needs lifetime specifiers:

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
$ cargo run
Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
--> src/main.rs:2:15
|
2 | username: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 | struct User<'a> {
2 | username: &'a str,
|

error[E0106]: missing lifetime specifier
--> src/main.rs:3:12
|
3 | email: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 | struct User<'a> {
2 | username: &str,
3 | email: &'a str,
|

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs`

To learn more, run the command again with --verbose.

In Chapter 10, we’ll discuss how to fix these errors so you can store references in structs, but for now, we’ll fix errors like these using owned types like String instead of references like &str.

An Example Program Using Structs

let’s write a program that calculates the area of a rectangle. We’ll start with single variables, and then refactor the program until we’re using structs instead.

1.功能的简单实现

1
2
3
4
5
6
7
8
9
fn main() {
let w1: u32 = 30;
let h1: u32 = 50;
println!("the rectangle is {}", area(w1,h1) );
}

fn area(width: u32, height: u32) -> u32 {
width * height
}

2.使用元组传值

1
2
3
fn area(wh_value: (u32, u32)) -> u32 {
wh_value.0 * wh_value.1
}

3.使用结构体

1
2
3
4
5
6
7
8
9
10
fn main() {
let tmp = tangel{width:30, height:50};
println!("the rectangle is {}", area(&tmp));
// println!("the rectangle is {}", area(
// &tangel{width:30, height:50}
//));
}
fn area( rectangle: &tangel ) -> u32 {
rectangle.width * rectangle.height
}

Adding Useful Functionality with Derived Traits

如何立即打印出结构体的所有值,直接打印报错:和类型不匹配

Putting the specifier :? inside the curly brackets tells println! we want to use an output format called Debug. The Debug trait enables us to print our struct in a way that is useful for developers so we can see its value while we’re debugging our code.

在程序头部加上#[derive(Debug)]进入debug程序模式

代码

1
2
3
4
5
6
7
8
9
10
11
#[derive(Debug)]
struct tangel {
width: u32,
height: u32,
}

fn main() {
let tmp = tangel{width:30, height:50};
println!("debug print {:?}",tmp);
println!("debug print {:#?}",tmp);
}

输出

1
2
3
4
5
debug print tangel { width: 30, height: 50 }
debug print tangel {
width: 30,
height: 50,
}

同时可以使用!dbg()来进行调试信息的输出。Calling the dbg! macro prints to the standard error console stream (stderr), as opposed to println! which prints to the standard output console stream (stdout).

代码

1
2
3
4
5
6
7
8
9
10
11
#[derive(Debug)]
struct tangel {
width: u32,
height: u32,
}

fn main() {
let tmp = tangel{width:30, height:50};
println!("debug print {:?}",tmp);
println!("debug print {:#?}",tmp);
}

输出

1
2
3
4
5
[src/main.rs:30] 30 * t = 60
[src/main.rs:33] tmp = tangel {
width: 60,
height: 50,
}

In addition to the Debug trait, Rust has provided a number of traits for us to use with the derive attribute that can add useful behavior to our custom types. Those traits and their behaviors are listed in Appendix C. We’ll cover how to implement these traits with custom behavior as well as how to create your own traits in Chapter 10. There are also many attributes other than derive; for more information, see the “Attributes” section of the Rust Reference.

如何将area从函数变为方法

Method Syntax

However, methods are different from functions in that they’re defined within the context of a struct (or an enum or a trait object, which we cover in Chapters 6 and 17, respectively), and their first parameter is always self, which represents the instance of the struct the method is being called on.(可以参考python类的构建方法)

Defining Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32{
self.width * self.height
}
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
  • The method syntax goes after an instance: we add a dot followed by the method name, parentheses, and any arguments.
  • Methods must have a parameter named self of type Self for their first parameter, so Rust lets you abbreviate this with only the name self in the first parameter spot.
  • If we wanted to change the instance that we’ve called the method on as part of what the method does, we’d use &mut self as the first parameter.

方法中的函数名能和成员名重合

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
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn width(&self) -> bool {
self.width > 0
}
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

if rect1.width() {
println!("The rectangle has a nonzero width; it is {}", rect1.width);
}
}

[命名规则]Often, but not always, methods with the same name as a field will be defined to only return the value in the field and do nothing else.

We will be discussing what public and private are and how to designate a field or method as public or private in Chapter 7.

Where’s the -> Operator?

In C and C++, two different operators are used for calling methods: you use . if you’re calling a method on the object directly and -> if you’re calling the method on a pointer to the object and need to dereference the pointer first. In other words, if object is a pointer, object->something() is similar to (*object).something().

Rust doesn’t have an equivalent to the -> operator; instead, Rust has a feature called automatic referencing and dereferencing. Calling methods is one of the few places in Rust that has this behavior.

Here’s how it works: when you call a method with object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method. In other words, the following are the same:

1
2
p1.distance(&p2);
(&p1).distance(&p2);

The first one looks much cleaner. This automatic referencing behavior works because methods have a clear receiver—the type of self. Given the receiver and name of a method, Rust can figure out definitively whether the method is reading (&self), mutating (&mut self), or consuming (self). The fact that Rust makes borrowing implicit for method receivers is a big part of making ownership ergonomic in practice.

Methods with More Parameters

例子任务: This time, we want an instance of Rectangle to take another instance of Rectangle and return true if the second Rectangle can fit completely within self; otherwise it should return false.

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
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn width(&self) -> bool {
self.width > 0
}

fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};

println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

Associated Functions

All functions defined within an impl block are called associated functions because they’re associated with the type named after the impl.

We can define associated functions that don’t have self as their first parameter (and thus are not methods) because they don’t need an instance of the type to work with.如String::from()

创建一个关联函数(任然要在impl中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn width(&self) -> bool {
self.width > 0
}

fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}

fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}

To call this associated function, we use the :: syntax with the struct name;

Multiple impl Blocks

可以在不同impl中对同一结构体对象的方法进行定义,虽然没有必要,但是在语法上可行

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
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}

impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}

fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};

println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

Summary

Structs let you create custom types that are meaningful for your domain. By using structs, you can keep associated pieces of data connected to each other and name each piece to make your code clear. In impl blocks, you can define functions that are associated with your type, and methods are a kind of associated function that let you specify the behavior that instances of your structs have.

But structs aren’t the only way you can create custom types: let’s turn to Rust’s enum feature to add another tool to your toolbox.

6-Enums and Pattern Matching

  • First, we’ll define and use an enum to show how an enum can encode meaning along with data.
  • Next, we’ll explore a particularly useful enum, called Option, which expresses that a value can be either something or nothing.
  • Then we’ll look at how pattern matching in the match expression makes it easy to run different code for different values of an enum.
  • Finally, we’ll cover how the if let construct is another convenient and concise idiom available to you to handle enums in your code.

Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell.

Defining an Enum

从一个例子来说明为什么枚举比结构体在某些情况下更加方便

These are the only possibilities for an IP address that our program will come across: we can enumerate all possible variants, which is where enumeration gets its name.

从定义一个IpAddrKind枚举开始来区分ipv4和ipv6

1
2
3
4
enum  IpAddrKind {
v4,
v6
}

Enum Values

创建枚举立即值

1
2
3
4
fn main() {
let four = IpAddrKind::v4;
let six = IpAddrKind::v6;
}

在这里 IpAddrKind::V4 and IpAddrKind::V6 都是同一个类型IpAddrKind

创建route函数来调用IpAddrKind

1
2
3
4
5
fn route(ip_kind: IpAddrKind) {}

fn main() {
route(IpAddrKind::v4);
}

at the moment we don’t have a way to store the actual IP address data; we only know what kind it is.

显示尝试使用简单的结构体解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::net::IpAddr;

enum IpAddrKind {
v4,
v6
}

struct MyIpAddr {
kind: IpAddrKind,
address: String,
}

fn main() {
let home = MyIpAddr {
kind: IpAddrKind::v4,
address: String::from("127.0.0.1")
};

let loopback = MyIpAddr {
kind: IpAddrKind::v6,
address: String::from("::1"),
};
}

我们已经使用结构体将种类和地址值捆绑在一起,所以现在变体与值相关联。

We can represent the same concept in a more concise way using just an enum, rather than an enum inside a struct, by putting data directly into each enum variant. 像是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::net::IpAddr;

enum IpAddrKind {
v4,
v6
}

enum MyIpAddr {
v4(String),
v6(String),
}

fn main() {
let home = MyIpAddr::v4(String::from("127.0.0.1"));
let loopback = MyIpAddr::v6(String::from("::1"));
}

Rust可以只能对值进行填充

1
2
3
4
5
6
7
8
9
10
fn main() {
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V6(String::from("::1"));
}

Rust的官方包对IpAdd这样定义

1
2
3
4
pub enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}

example

1
2
3
4
5
6
7
8
9
10
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let localhost_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));

assert_eq!("127.0.0.1".parse(), Ok(localhost_v4));
assert_eq!("::1".parse(), Ok(localhost_v6));

assert_eq!(localhost_v4.is_ipv6(), false);
assert_eq!(localhost_v4.is_ipv4(), true);

This code illustrates that you can put any kind of data inside an enum variant: strings, numeric types, or structs, for example.

利用枚举简化结构体

1
2
3
4
5
6
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

功能等价于

1
2
3
4
5
6
7
8
9
struct QuitMessage; // unit struct
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

fn main() {}

There is one more similarity between enums and structs: just as we’re able to define methods on structs using impl, we’re also able to define methods on enums.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

impl Message {
fn call(&self) {
// println!("world");
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}

The Option Enum and Its Advantages Over Null Values

This section explores a case study of Option, which is another enum defined by the standard library. The Option type is used in many places because it encodes the very common scenario in which a value could be something or it could be nothing.

this functionality can prevent bugs that are extremely common in other programming languages.

Rust doesn’t have the null feature that many other languages have.

I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is Option<T>, and it is defined by the standard library as follows:

1
2
3
4
enum Option<T> {
None,
Some(T),
}

使用含null的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Option<T> {
None,
Some(T),
}


fn main() {
//using null enum
let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;
}

其中<T>是类似于C++中的泛型

In short, because Option<T> and T (where T can be any type) are different types, the compiler won’t let us use an Option<T> value as if it were definitely a valid value. For example, this code won’t compile because it’s trying to add an i8 to an Option<i8>:

1
2
3
4
let x: i8 = 5;
let y: Option<i8> = Some(5);

let sum = x + y;

In other words, you have to convert an Option<T> to a T before you can perform T operations with it.

So, how do you get the T value out of a Some variant when you have a value of type Option<T> so you can use that value? The Option<T> enum has a large number of methods that are useful in a variety of situations; you can check them out in its documentation. Becoming familiar with the methods on Option<T> will be extremely useful in your journey with Rust.

In general, in order to use an Option<T> value, you want to have code that will handle each variant. You want some code that will run only when you have a Some(T) value, and this code is allowed to use the inner T. You want some other code to run if you have a None value, and that code doesn’t have a T value available. The match expression is a control flow construct that does just this when used with enums: it will run different code depending on which variant of the enum it has, and that code can use the data inside the matching value.

The match Control Flow Operator

Rust has an extremely powerful control flow operator called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches.

如何具体化match的思想

1
Think of a match expression as being like a coin-sorting machine: coins slide down a track with variously sized holes along it, and each coin falls through the first hole it encounters that it fits into. In the same way, values go through each pattern in a match, and at the first pattern the value “fits,” the value falls into the associated code block to be used during execution.

然后就有了下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}

fn main() {
// let coin = Coin::Penny;
println!("Penny {}",value_in_cents(Coin::Penny));
println!("Nickel {}",value_in_cents(Coin::Nickel));
println!("Dime {}",value_in_cents(Coin::Dime));
println!("Quarter {}",value_in_cents(Coin::Quarter));
}

Next are the match arms. An arm has two parts: a pattern and some code. The first arm here has a pattern that is the value Coin::Penny and then the => operator that separates the pattern and the code to run. The code in this case is just the value 1. Each arm is separated from the next with a comma.

If that pattern doesn’t match the value, execution continues to the next arm, much as in a coin-sorting machine.

Patterns that Bind to Values

对枚举值进行键值绑定

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
#[derive(Debug)]
// enum Coin {
// Penny,
// Nickel,
// Dime,
// Quarter,
// }
enum UsState {
Alabama,
Alaska,
// --snip--
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State Quarter from {:?}!",state);
25
},
}
}

fn main() {

// let coin = Coin::Penny;
println!("Penny {}",value_in_cents(Coin::Penny));
println!("Nickel {}",value_in_cents(Coin::Nickel));
println!("Dime {}",value_in_cents(Coin::Dime));
println!("Quarter {}",value_in_cents(Coin::Quarter(UsState::Alaska)));
}

Matching with Option

we can also handle Option<T> using match as we did with the Coin enum! Instead of comparing coins, we’ll compare the variants of Option<T>, but the way that the match expression works remains the same.

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

Matches Are Exhaustive

There’s one other aspect of match we need to discuss. Consider this version of our plus_one function that has a bug and won’t compile:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

We didn’t handle the None case, so this code will cause a bug.

使用_来接受其他情况

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}

fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
}

等于

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}

fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
}

Concise Control Flow with if let

如果有如下代码

1
2
3
4
5
6
7
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}

可以被if let 简化为

1
2
3
4
5
6
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
}

If we wanted to count all non-quarter coins we see while also announcing the state of the quarters, we could do that with a match expression like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn main() {
let coin = Coin::Penny;
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
}

Or we could use an if let and else expression like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}

enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}

fn main() {
let coin = Coin::Penny;
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
}

Summary

We’ve now covered how to use enums to create custom types that can be one of a set of enumerated values. We’ve shown how the standard library’s Option<T> type helps you use the type system to prevent errors. When enum values have data inside them, you can use match or if let to extract and use those values, depending on how many cases you need to handle.

Your Rust programs can now express concepts in your domain using structs and enums. Creating custom types to use in your API ensures type safety: the compiler will make certain your functions get only values of the type each function expects.

In order to provide a well-organized API to your users that is straightforward to use and only exposes exactly what your users will need, let’s now turn to Rust’s modules.

7-Managing Growing Projects with Packages, Crates, and Modules

The programs we’ve written so far have been in one module in one file.

once you’ve implemented an operation, other code can call that code via the code’s public interface without knowing how the implementation works.

These features, sometimes collectively referred to as the module system模块系统, include:

  • Packages: A Cargo feature that lets you build, test, and share crates
  • Crates: A tree of modules that produces a library or executable
  • Modules and use: Let you control the organization, scope, and privacy of paths
  • Paths: A way of naming an item, such as a struct, function, or module

Packages and Crates

Let’s walk through what happens when we create a package. First, we enter the command cargo new:

  • Cargo.toml
    • Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package.
    • Likewise, Cargo knows that if the package directory contains src/lib.rs, the package contains a library crate with the same name as the package, and src/lib.rs is its crate root.
    • Here, we have a package that only contains src/main.rs, meaning it only contains a binary crate named my-project.
    • A package can have multiple binary crates by placing files in the src/bin directory: each file will be a separate binary crate.

Defining Modules to Control Scope and Privacy

src/lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}

fn seat_at_table() {}
}

mod serving {
fn take_order() {}

fn serve_order() {}

fn take_payment() {}
}
}

We define a module by starting with the mod keyword and then specify the name of the module (in this case, front_of_house)

By using modules, we can group related definitions together and name why they’re related.

Listing 7-2 shows the module tree for the structure in Listing 7-1.

1
2
3
4
5
6
7
8
9
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment

这个说明了模式是可以嵌套的,可以类比计算机的文件系统

Paths for Referring to an Item in the Module Tree

A path can take two forms:

  • An absolute path starts from a crate root by using a crate name or a literal crate.
  • A relative path starts from the current module and uses self, super, or an identifier in the current module.

Let’s return to the example in Listing 7-1. How do we call the add_to_waitlist function?

using the crate name to start from the crate root is like using / to start from the filesystem root in your shell.

该程序是有问题的

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();

// Relative path
front_of_house::hosting::add_to_waitlist();
}

Our preference is to specify absolute paths because it’s more likely to move code definitions and item calls independently of each other

The pub keyword on a module only lets code in its ancestor modules refer to it.

rust默认模式是私有的

如何访问

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();

// Relative path
front_of_house::hosting::add_to_waitlist();
}

Starting Relative Paths with super

We can also construct relative paths that begin in the parent module by using super at the start of the path. super访问上级目录的函数,有点像..的访问

1
2
3
4
5
6
7
8
9
10
fn serve_order() {}

mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}

fn cook_order() {}
}

Making Structs and Enums Public

同样也是pub的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("Peaches"),
}
}
}
}

pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
meal.toast = String::from("Wheat");
println!("I'd like {} toast please",meal.toast);
}

Bringing Paths into Scope with the use Keyword

对每次都要调用的函数,有更简单的方法来避免rust的重复寻址

像是从

1
2
3
4
5
6
7
8
9
10
11
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

pub fn eat_at_restaurant() {
front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
}

可以使用use关键字变为

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

Creating Idiomatic use Paths

Bringing the function’s parent module into scope with use means we have to specify the parent module when calling the function.

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}

如果有多个类中定义了add_to_waitlist函数,上面的使用会让我们不知道eat_at_restaurant的中的是属于哪个类

下面的例子使用标准库中的哈希函数计算来使用惯用方式管理hash结构体

src/main.rs

1
2
3
4
5
6
use std::collections::HashMap;

fn main() {
let mut map = HashMap::new();
map.insert(1, 1);
}

There’s no strong reason behind this idiom: it’s just the convention that has emerged, and folks have gotten used to reading and writing Rust code this way.

Listing 7-15 shows how to bring two Result types into scope that have the same name but different parent modules and how to refer to them.

1
2
3
4
5
6
7
8
9
10
11
12
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
// --snip--
Ok(())
}

fn function2() -> io::Result<()> {
// --snip--
Ok(())
}

Providing New Names with the as Keyword

和python的as关键字用法差不多

Re-exporting Names with pub use

To enable the code that calls our code to refer to that name as if it had been defined in that code’s scope, we can combine pub and use.

1
2
3
4
5
6
7
8
9
10
11
12
13
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

Re-exporting is useful when the internal structure of your code is different from how programmers calling your code would think about the domain.

Using External Packages

就像章节2中说到的那样,需要在toml中加入库

1
rand = "0.8.3"

之后就可以使用了

Using Nested Paths to Clean Up Large use Lists

当你一次要导入多个mod中的fn时,可以使用如下嵌套(Go抄了属于是)

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
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--

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!"),
}
}

甚至可以把自身嵌套使用

1
use std::io::{self, Write};

The Glob Operator

使用mod中的全部内容,类似python的from libs import *,rust有

1
2
3
4
#![allow(unused)]
fn main() {
use std::collections::*;
}

Separating Modules into Different Files

For example, let’s start from the code in Listing 7-17 and move the front_of_house module to its own file src/front_of_house.rs by changing the crate root file so it contains the code shown in Listing 7-21. In this case, the crate root file is src/lib.rs, but this procedure also works with binary crates whose crate root file is src/main.rs.

首先是src/lib.rs

1
2
3
4
5
6
7
8
9
mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

接着是src/front_of_house.rs

1
2
3
pub mod hosting {
pub fn add_to_waitlist() {}
}

最后在在src/front_of_house/hosting.rs中实现add_to_waitlist

1
2
3
4
#![allow(unused)]
fn main() {
pub fn add_to_waitlist() {}
}

Summary

Rust lets you split a package into multiple crates and a crate into modules so you can refer to items defined in one module from another module. You can do this by specifying absolute or relative paths. These paths can be brought into scope with a use statement so you can use a shorter path for multiple uses of the item in that scope. Module code is private by default, but you can make definitions public by adding the pub keyword.

In the next chapter, we’ll look at some collection data structures in the standard library that you can use in your neatly organized code.

8-Common Collections

rust官方库有些很有用的数据结构被称作为’collections’.

这些collections指针是被存储在heap中的

感觉类似于c++的STL中的容器

  • A vector allows you to store a variable number of values next to each other.
  • A string is a collection of characters. We’ve mentioned the String type previously, but in this chapter we’ll talk about it in depth.
  • A hash map allows you to associate a value with a particular key. It’s a particular implementation of the more general data structure called a map.

Storing Lists of Values with Vectors

本节教程教学vector容器

1
2
3
fn main() {
let v: Vec<i32> = Vec::new();
}

Vectors are implemented using generics,相当于创建了一个vector指针

1
2
//给vector创建初始值
let v2 = vec![1,2,3];

Updating a Vector

使用push函数

1
2
3
4
5
//Update a Vector
v.push(5);
v.push(6);
v.push(7);
v.push(8);

if we want to be able to change its value, we need to make it mutable using the mut keyword, as discussed in Chapter 3.

Dropping a Vector Drops Its Elements

Like any other struct, a vector is freed when it goes out of scope, as annotated in Listing 8-4.

当一个vector被释放时,它的成员也会被释放掉

Reading Elements of Vectors

两种访问vector元素的方法

1
2
3
4
5
6
7
8
let v2 = vec![1,2,3,4,5];
let third: &i32 = &v2[2];
println!("the third ele is {}",third);

match v2.get(2) {
Some(third) => println!("The third ele is {}",third),
None => println!("no third ele"),
}
  1. 使用直接引用&v2[idx]

  2. 使用get方法后,使用match来判断元素不存

当直接下标访问时,若元素不存在,会直接报错

关于vector引用,同样会因为reference 和 borrow原则出现报错,如

1
2
3
4
5
6
7
8
9
fn main() {
let mut v = vec![1, 2, 3, 4, 5];

let first = &v[0];

v.push(6);

println!("The first element is: {}", first);
}

原因时first创建了一个不可变的borrow,同时v.push方法有使用了可变的borrow。

Iterating over the Values in a Vector

利用迭代器访问容器

最简单的是使用for语句

1
2
3
4
5
6
7
8
9
10
11
{
let v = vec![100,32,57];
for i in &v{
println!("{}",i);
}

let mut v = vec![100,32,57];
for i in &mut v {
*i += 50;
}
}

这里要对v中元素值进行修改,要使用*解引用才能访问到数值并修改

We’ll talk more about the dereference operator in the “Following the Pointer to the Value with the Dereference Operator”

Using an Enum to Store Multiple Types

say we want to get values from a row in a spreadsheet in which some of the columns in the row contain integers, some floating-point numbers, and some strings. We can define an enum whose variants will hold the different value types, and then all the enum variants will be considered the same type: that of the enum. Then we can create a vector that holds that enum and so, ultimately, holds different types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Part`3: Using an Enum to Store Multiple Types
{
enum SperateSheetCell {
Int(i32),
Float(f64),
Text(String),
}

let row = vec![
SperateSheetCell::Int(3),
SperateSheetCell::Float(30.0),
SperateSheetCell::Text(String::from("hello")),
]
}

Rust needs to know what types will be in the vector at compile time so it knows exactly how much memory on the heap will be needed to store each element.

Storing UTF-8 Encoded Text with Strings

What Is a String?

We’ll first define what we mean by the term string. Rust has only one string type in the core language, which is the string slice str that is usually seen in its borrowed form &str. In Chapter 4, we talked about string slices, which are references to some UTF-8 encoded string data stored elsewhere. String literals, for example, are stored in the program’s binary and are therefore string slices.

The String type, which is provided by Rust’s standard library rather than coded into the core language, is a growable, mutable, owned, UTF-8 encoded string type. When Rustaceans refer to “strings” in Rust, they usually mean the String and the string slice &str types, not just one of those types. Although this section is largely about String, both types are used heavily in Rust’s standard library, and both String and string slices are UTF-8 encoded.

Rust’s standard library also includes a number of other string types, such as OsString, OsStr, CString, and CStr. Library crates can provide even more options for storing string data. See how those names all end in String or Str? They refer to owned and borrowed variants, just like the String and str types you’ve seen previously. These string types can store text in different encodings or be represented in memory in a different way, for example. We won’t discuss these other string types in this chapter; see their API documentation for more about how to use them and when each is appropriate.

什么是字符串?
我们将首先定义术语字符串的含义。 Rust 在核心语言中只有一种字符串类型,即通常以借用形式 &str 看到的字符串切片 str。在第 4 章中,我们讨论了字符串切片,它是对存储在其他地方的一些 UTF-8 编码字符串数据的引用。例如,字符串文字存储在程序的二进制文件中,因此是字符串切片。

String 类型由 Rust 的标准库提供而不是编码到核心语言中,它是一种可增长、可变、拥有、UTF-8 编码的字符串类型。当 Rustaceans 在 Rust 中提到“字符串”时,他们通常指的是 String 和字符串 slice &str 类型,而不仅仅是这些类型中的一种。虽然本节主要是关于字符串,但这两种类型在 Rust 的标准库中都被大量使用,并且字符串和字符串切片都是 UTF-8 编码的。

Rust 的标准库还包括许多其他字符串类型,例如 OsStringOsStrCString CStr。 Library crate 可以提供更多选项来存储字符串数据。看看这些名字都是如何以 String 或 Str 结尾的?它们指的是拥有和借用的变体,就像您之前看到的 String str 类型一样。例如,这些字符串类型可以以不同的编码存储文本或以不同的方式在内存中表示。我们不会在本章中讨论这些其他字符串类型;请参阅他们的 API 文档,了解有关如何使用它们以及何时使用它们的更多信息。

Creating a New String

和创建vector差不多

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

This line creates a new empty string called s, which we can then load data into.

1
2
3
4
5
6
7
8
9
10
11
{
//创建一个字符串类型
let mut s = String::new();//s类型是String

//str转String
let data = "initial contents";

let s = data.to_string();

let s = "initial contents2".to_string();
}

还有就是最常用的String::from()

Updating a String

if you push more data into it. In addition, you can conveniently use the + operator or the format! macro to concatenate String values.

Appending to a String with push_str and push

  • We can grow a String by using the push_str method to append a string slice

    1
    2
    let mut s = String::from("foo");
    s.push_str("bar");

    The push_str method takes a string slice because we don’t necessarily want to take ownership of the parameter.

    关于从属关系

    1
    2
    3
    4
    5
    6
    fn main() {
    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {}", s2);
    }
  • The push method takes a single character as a parameter and adds it to the String.

    1
    2
    let mut s = String::from("lo");
    s.push('l');

Concatenation with the + Operator or the format! Macro

  • 使用+连接两个字符串

    1
    2
    3
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used

    此时的s3就是原来的s1,原因如下

    The reason s1 is no longer valid after the addition and the reason we used a reference to s2 has to do with the signature of the method that gets called when we use the + operator. The + operator uses the add method, whose signature looks something like this:

    1
    fn add(self, s: &str) -> String {

    But wait—the type of &s2 is &String, not &str, as specified in the second parameter to add. So why does Listing 8-18 compile?

    the compiler can coerce the &String argument into a &str.//强制转换

    we can see in the signature that add takes ownership of self, because self does not have an &. This means s1 in Listing 8-18 will be moved into the add call and no longer be valid after that.

  • If we need to concatenate multiple strings, the behavior of the + operator gets unwieldy:

    1
    2
    3
    4
    5
    6
    7
    fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = s1 + "-" + &s2 + "-" + &s3;
    }

Indexing into Strings

Rust strings don’t support indexing.

Internal Representation

在rust中,String可以存储unicode类型,不同类型的存储长度导致无法找到位置

What should the value of answer be? Should it be З, the first letter? When encoded in UTF-8, the first byte of З is 208 and the second is 151, so answer should in fact be 208, but 208 is not a valid character on its own. Returning 208 is likely not what a user would want if they asked for the first letter of this string; however, that’s the only data that Rust has at byte index 0. Users generally don’t want the byte value returned, even if the string contains only Latin letters: if &"hello"[0] were valid code that returned the byte value, it would return 104, not h. To avoid returning an unexpected value and causing bugs that might not be discovered immediately, Rust doesn’t compile this code at all and prevents misunderstandings early in the development process.

Bytes and Scalar Values and Grapheme Clusters! Oh My!

Another point about UTF-8 is that there are actually three relevant ways to look at strings from Rust’s perspective: as bytes, scalar values, and grapheme clusters

Rust provides different ways of interpreting the raw string data that computers store so that each program can choose the interpretation it needs, no matter what human language the data is in.

Slicing Strings

Rust asks you to be more specific if you really need to use indices to create string slices. To be more specific in your indexing and indicate that you want a string slice, rather than indexing using [] with a single number, you can use [] with a range to create a string slice containing particular bytes:

1
2
let hello = "Здравствуйте";
let s = &hello[0..4];

Methods for Iterating Over Strings

If you need to perform operations on individual Unicode scalar values, the best way to do so is to use the chars method.

使用chars函数

1
2
3
for c in "नमस्ते".chars() {
println!("{}", c);
}

The bytes method returns each raw byte, which might be appropriate for your domain:

1
2
3
for b in "नमस्ते".bytes() {
println!("{}", b);
}

结果

1
2
3
4
5
224
164
// --snip--
165
135

Strings Are Not So Simple

To summarize, strings are complicated. Different programming languages make different choices about how to present this complexity to the programmer. Rust has chosen to make the correct handling of String data the default behavior for all Rust programs, which means programmers have to put more thought into handling UTF-8 data upfront. This trade-off exposes more of the complexity of strings than is apparent in other programming languages, but it prevents you from having to handle errors involving non-ASCII characters later in your development life cycle.

Storing Keys with Associated Values in Hash Maps

Creating a New Hash Map

同上面两个差不多,使用new创建,insert加入值

1
2
3
4
5
use std::collections::HashMap;
let mut socers = HashMap::new();

socers.insert(String::from("Blue"),10);
socers.insert(String::from("Yellow"), 50);

创建了Buel:10,Yellow:50,两个kv

使用iterators and the collect method on a vector of tuples, where each tuple consists of a key and its value.

1
2
3
4
5
6
use std::collections::HashMap;
let teams = vec![String::from("Bule"),String::from("Yellow")];
let initial_scores = vec![10,50];

let mut scores: HashMap<_,_> =
teams.into_iter().zip(initial_scores.into_iter()).collect();

Hash Maps and Ownership

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

let field_name = String::from("Favorite color");
let field_value = String::from("Blue");

let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point, try using them and
// see what compiler error you get!

这样的话,field_namefield_value会变成map的从属,就不能再被访问到了

Accessing Values in a Hash Map

使用get方法

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
let score = scores.get(&team_name);

the result will be Some(&10). The result is wrapped in Some because get returns an Option<&V>; if there’s no value for that key in the hash map, get will return None. The program will need to handle the Option in one of the ways that we covered in Chapter 6.

使用for循环访问

1
2
3
4
5
6
7
8
9
10
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

for (key, value) in &scores {
println!("{}: {}", key, value);
}

直接使用引用访问

Updating a Hash Map

Overwriting a Value

直接使用insert即可覆盖

1
2
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

Only Inserting a Value If the Key Has No Value

Hash maps have a special API for this called entry that takes the key you want to check as a parameter.

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores);

Updating a Value Based on the Old Value

Listing 8-26 shows code that counts how many times each word appears in some text.

1
2
3
4
5
6
7
8
9
10
11
12
13
//update based on old value
use std::collections::HashMap;

let text = "hello world wonderful world";

let mut map = HashMap::new();

for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}

println!("{:?}", map);

Hashing Functions

By default, HashMap uses a hashing function called SipHash that can provide resistance to Denial of Service (DoS) attacks involving hash tables1. This is not the fastest hashing algorithm available, but the trade-off for better security that comes with the drop in performance is worth it. If you profile your code and find that the default hash function is too slow for your purposes, you can switch to another function by specifying a different hasher. A hasher is a type that implements the BuildHasher trait. We’ll talk about traits and how to implement them in Chapter 10. You don’t necessarily have to implement your own hasher from scratch; crates.io has libraries shared by other Rust users that provide hashers implementing many common hashing algorithms.

Summary

Vectors, strings, and hash maps will provide a large amount of functionality necessary in programs when you need to store, access, and modify data. Here are some exercises you should now be equipped to solve:

  • Given a list of integers, use a vector and return the mean (the average value), median (when sorted, the value in the middle position), and mode (the value that occurs most often; a hash map will be helpful here) of the list.
  • Convert strings to pig latin. The first consonant of each word is moved to the end of the word and “ay” is added, so “first” becomes “irst-fay.” Words that start with a vowel have “hay” added to the end instead (“apple” becomes “apple-hay”). Keep in mind the details about UTF-8 encoding!
  • Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in a company. For example, “Add Sally to Engineering” or “Add Amir to Sales.” Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.

The standard library API documentation describes methods that vectors, strings, and hash maps have that will be helpful for these exercises!

We’re getting into more complex programs in which operations can fail, so, it’s a perfect time to discuss error handling. We’ll do that next!

9-Error Handling

In many cases, Rust requires you to acknowledge the possibility of an error and take some action before your code will compile.

Rust groups errors into two major categories: recoverable and unrecoverable errors.

  • recoverable

    such as a file not found error, it’s reasonable to report the problem to the user and retry the operation.

  • Unrecoverable

    always symptoms of bugs, like trying to access a location beyond the end of an array.

Rust doesn’t have exceptions. Instead, it has the type Result<T, E> for recoverable errors and the panic! macro that stops execution when the program encounters an unrecoverable error.

Unrecoverable Errors with panic!

When the panic! macro executes, your program will print a failure message, unwind and clean up the stack, and then quit.

1
2
3
fn main() {
panic!("crash and burn");
}
1
2
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The first line shows our panic message and the place in our source code where the panic occurred: src/main.rs:2:5 indicates that it’s the second line, fifth character of our src/main.rs file.

In other cases, the panic! call might be in code that our code calls, and the filename and line number reported by the error message will be someone else’s code where the panic! macro is called, not the line of our code that eventually led to the panic! call.

Using a panic! Backtrace

利用错误代码实现函数反追踪

1
2
3
4
5
fn main() {
let v = vec![1, 2, 3];

v[99];
}
1
2
3
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 
99', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

A backtrace is a list of all the functions that have been called to get to this point.

RUST_BACKTRACE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
joe1sn@MSI:/mnt/d/Programming/Rust/The Rust Programming Language/9-Error Handling/unrecoverable_error$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.09s
Running `target/debug/unrecoverable_error`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
99', src/main.rs:3:5
stack backtrace:
0: rust_begin_unwind
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/std/src/panicking.rs:517:5
1: core::panicking::panic_fmt
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:101:14
2: core::panicking::panic_bounds_check
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:77:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/slice/index.rs:184:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/slice/index.rs:15:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/alloc/src/vec/mod.rs:2465:9
6: unrecoverable_error::main
at ./src/main.rs:3:5
7: core::ops::function::FnOnce::call_once
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Debug symbols are enabled by default when using cargo build or cargo run without the --release flag, as we have here.

We’ll come back to panic! and when we should and should not use panic! to handle error conditions in the “To panic! or Not to panic! section later in this chapter. Next, we’ll look at how to recover from an error using Result.

Recoverable Errors with Result

Recall from “Handling Potential Failure with the Result Type” in Chapter 2 that the Result enum is defined as having two variants, Ok and Err, as follows:

1
2
3
4
5
6
fn main() {
enum Result<T, E> {
Ok(T),
Err(E),
}
}

创建一个一定会出错的程序

1
let f = File::open("hello.txt");

match的验证代码

1
2
3
4
5
6
7
8
9
10
use std::fs::File;

fn main() {
let f = File::open("hello.txt");

let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
1
2
thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Matching on Different Errors

if File::open failed because the file doesn’t exist, we want to create the file and return the handle to the new file.

If File::open failed for any other reason—for example, because we didn’t have permission to open the file—we still want the code to panic! in the same way as it did in Listing 9-4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::fs::File;
use std::io::ErrorKind;

fn main() {
let f = File::open("hello.txt");

let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating new file"),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}

In Chapter 13, you’ll learn about closures; the Result<T, E> type has many methods that accept a closure and are implemented using match expressions. Using those methods will make your code more concise. A more seasoned Rustacean might write this code instead of Listing 9-5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use std::fs::File;
use std::io::ErrorKind;

fn main() {
let f = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}

Shortcuts for Panic on Error: unwrap and expect

unwrap

1
2
3
4
5
use std::fs::File;

fn main() {
let f = File::open("hello.txt").unwrap();
}

If we run this code without a hello.txt file, we’ll see an error message from the panic! call that the unwrap method makes:

expect

Using expect instead of unwrap and providing good error messages can convey your intent and make tracking down the source of a panic easier.

1
2
3
4
5
use std::fs::File;

fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

Propagating Errors

instead of handling the error within this function, you can return the error to the calling code so that it can decide what to do. This is known as propagating the error and gives more control to the calling code, where there might be more information or logic that dictates how the error should be handled than what you have available in the context of your code.

For example, Listing 9-6 shows a function that reads a username from a file. If the file doesn’t exist or can’t be read, this function will return those errors to the code that called this function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");

let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};

let mut s = String::new();

match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
}

A Shortcut for Propagating Errors: the ? Operator

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();

File::open("hello.txt")?.read_to_string(&mut s)?;

Ok(s)
}
}
1
2
3
4
5
6
7
8
fn main() {
use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
}

The ? Operator Can Be Used in Functions That Return Result

报错程序

1
2
3
4
5
use std::fs::File;

fn main() {
let f = File::open("hello.txt")?;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:36
|
3 | / fn main() {
4 | | let f = File::open("hello.txt")?;
| | ^ cannot use the `?` operator in a function that returns `()`
5 | | }
| |_- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
= note: required by `from_residual`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling`

To learn more, run the command again with --verbose.

The main function is special, and there are restrictions on what its return type must be. One valid return type for main is (), and conveniently, another valid return type is Result<T, E>, as shown here:

1
2
3
4
5
6
7
8
use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;

Ok(())
}

To panic! or Not to panic!

Summary

Rust’s error handling features are designed to help you write more robust code. The panic! macro signals that your program is in a state it can’t handle and lets you tell the process to stop instead of trying to proceed with invalid or incorrect values. The Result enum uses Rust’s type system to indicate that operations might fail in a way that your code could recover from. You can use Result to tell code that calls your code that it needs to handle potential success or failure as well. Using panic! and Result in the appropriate situations will make your code more reliable in the face of inevitable problems.

Now that you’ve seen useful ways that the standard library uses generics with the Option and Result enums, we’ll talk about how generics work and how you can use them in your code.

10-Generic Types, Traits, and Lifetimes

rust使用泛型来对 duplication of concepts 处理

When we’re writing code, we can express the behavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code.

一些之前使用过的例子

like i32 or String. In fact, we’ve already used generics in Chapter 6 with Option<T>, Chapter 8 with Vec<T> and HashMap<K, V>, and Chapter 9 with Result<T, E>.

rust使用特征traits 来定义泛型的行为

  • First, we’ll review how to extract a function to reduce code duplication.
  • Then you’ll learn how to use traits to define behavior in a generic way.
  • Finally, we’ll discuss lifetimes, a variety of generics that give the compiler information about how references relate to each other.

Removing Duplication by Extracting a Function

Before diving into generics syntax, let’s first look at how to remove duplication that doesn’t involve generic types by extracting a function.

src/main.rs

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let number_list = vec![34,35,654,234,12,23];
let mut largest = number_list[0];
for number in number_list {
if number > largest {
largest = number;
}
}

println!("The largest number is {}", largest);
}

现在要得到两个数列中的最大数

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
fn main() {
let number_list = vec![34, 50, 25, 100, 65];

let mut largest = number_list[0];

for number in number_list {
if number > largest {
largest = number;
}
}

println!("The largest number is {}", largest);

let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

let mut largest = number_list[0];

for number in number_list {
if number > largest {
largest = number;
}
}

println!("The largest number is {}", largest);
}

其中最开始的

1
2
3
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

let mut largest = number_list[0];

会被重复定义

这个时候定义成函数

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
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];

for &item in list {
if item > largest {
largest = item;
}
}

largest
}

fn main() {
let number_list = vec![34, 50, 25, 100, 65];

let result = largest(&number_list);
println!("The largest number is {}", result);
assert_eq!(result, 100);

let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

let result = largest(&number_list);
println!("The largest number is {}", result);
assert_eq!(result, 6000);
}

In sum, here are the steps we took to change the code from Listing 10-2 to Listing 10-3:

  1. Identify duplicate code.
  2. Extract the duplicate code into the body of the function and specify the inputs and return values of that code in the function signature.
  3. Update the two instances of duplicated code to call the function instead.

Generic Data Types

第一时间想到的肯定是c++里面的泛型

测试程序如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];

for &item in list {
if item > largest {
largest = item;
}
}

largest
}

fn main() {
let number_list = vec![34, 50, 25, 100, 65];

let result = largest(&number_list);
println!("The largest number is {}", result);

let char_list = vec!['y', 'm', 'a', 'q'];

let result = largest(&char_list);
println!("The largest char is {}", result);
}
1
2
3
4
5
6
error[E0369]: binary operation `>` cannot be applied to type `T`
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++

其中std::cmp::PartialOrd就是泛型的一个特征

Because we want to compare values of type T in the body, we can only use types whose values can be ordered. To enable comparisons, the standard library has the std::cmp::PartialOrd trait that you can implement on types (see Appendix C for more on this trait).

In Struct Definitions

泛型结构体

1
2
3
4
5
6
7
8
9
struct Point<T> {
x: T,
y: T,
}

fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}

If we create an instance of a Point<T> that has values of different types, as in Listing 10-7, our code won’t compile.

解决这个问题可以使用

1
2
3
4
struct Point<T, U> {
x: T,
y: U,
}

In Enum Definitions

略。。。

Traits: Defining Shared Behavior

We want to make a media aggregator library that can display summaries of data that might be stored in a NewsArticle or Tweet instance. To do this, we need a summary from each type, and we need to request that summary by calling a summarize method on an instance. Listing 10-12 shows the definition of a Summary trait that expresses this behavior.

1
2
3
pub trait Summary {
fn summarize(&self) -> String;
}

Each type implementing this trait must provide its own custom behavior for the body of the method. The compiler will enforce that any type that has the Summary trait will have the method summarize defined with this signature exactly.

Implementing a Trait on a Type

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
pub trait Summary {
fn summarize(&self) -> String;
}

pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}

impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

Validating References with Lifetimes

Summary

We covered a lot in this chapter! Now that you know about generic type parameters, traits and trait bounds, and generic lifetime parameters, you’re ready to write code without repetition that works in many different situations. Generic type parameters let you apply the code to different types. Traits and trait bounds ensure that even though the types are generic, they’ll have the behavior the code needs. You learned how to use lifetime annotations to ensure that this flexible code won’t have any dangling references. And all of this analysis happens at compile time, which doesn’t affect runtime performance!

Believe it or not, there is much more to learn on the topics we discussed in this chapter: Chapter 17 discusses trait objects, which are another way to use traits. There are also more complex scenarios involving lifetime annotations that you will only need in very advanced scenarios; for those, you should read the Rust Reference. But next, you’ll learn how to write tests in Rust so you can make sure your code is working the way it should.

后面的基本上就跳者来了,从项目中学代码