1.包、Crate和模块

rust的模块系统包括:

  • 包: cargo的一个功能,它允许你构建、测试、分享crate。一个包可以包含多个二进制crate和一个可选的库crate
  • crate: 一个模块的树形结构,它形成了库或二进制项目
  • 模块(mod)和use: 模块通过use来使用,允许你控制作用域和路径的私有性
  • 路径(path): 一个命名例如结构体、函数或模块等项的方式

1.1 包和crate

crate是一个二进制项或库。 crate root是一个源文件,crate 是一个二进制项或者库。crate root 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块。 crate将一个作用域相关的功能分组到一起,使得这些功能可以方便的在多个项目间共享。

包提供一系列功能的一个或多个crate。一个包会包含有一个 Cargo.toml 文件,阐述如何去构建这些 crate。 包中所包含的内容由几条规则来确立。一个包中至多 只能包含一个库crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个crate,无论是库的还是二进制的。

cargo new命令实际上是创建了一个package:

1
2
3
4
5
6
7
8
cargo new c10_package
     Created binary (application) `c10_package` package

tree c10_package
c10_package
├── Cargo.toml
└── src
    └── main.rs

此时我们创建了一个只包含src/main.rs的包,表示它只包含一个名称为c10_package的二进制crate。 如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate(一个库和一个二进制项),且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。

1.2 定义模块控制作用域和私有性

模块让我们可以将一个crate中的代码进行分组,以提高可读性与重用性。模块还可以控制项的私有性,crate中所有项(模块、函数、结构体、方法、枚举、常量)都是私有的,需要使用pub修饰才能暴露给外部。

创建一个lib可以通过命令cargo new --lib libname来进行。我们在c10_package下使用cargo new --lib restaurant创建一个新的名称为restaurant的库:

1
2
3
4
5
6
7
8
c10_package
├── Cargo.toml
├── restaurant
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

在lib.rs中创建模块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pub mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}

        pub fn seat_at_table() {}
    }

    pub mod serving {
        pub fn take_order() {}

        pub fn server_order() {}

        pub fn take_payment() {}
    }
}
pub fn eat_at_restaurant() {
    // 绝对逻辑使用模块中的项
    crate::front_of_house::hosting::add_to_waitlist();
    // 相对路径使用模块中的项
    front_of_house::hosting::add_to_waitlist();
}

1.3 使用use关键字将名称引入作用域

1
2
3
4
5
6
7
8
use crate::front_of_house::hosting;
use crate::front_of_house::hosting::add_to_waitlist as add_to;


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

2.常见集合

2.1 vector

vector允许在一个单独的数据结构中储存多于一个的值,所有值在内存中彼此相邻排列。vector中只能储存相同类型的值。

创建vector的方式:

1
2
3
let mut nums :Vec<i32> = Vec::new();

let nums = vec![1, 2, 3];

向vector中添加元素:

1
2
3
4
5
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);

丢弃vector时也会丢弃其所有元素:

1
2
3
4
5
6
 let str = String::from("hello");
{
    let mut v :Vec<String> = Vec::new();
    v.push(str)
} // v离开作用域,其中的str也被一起被丢弃
println!("{}", str); // 编译错误 borrow of moved value: `str`

读取vector中的元素, 方法1索引语法:

1
2
3
let one :&i32 = &nums[0];
println!("nums[0]={}", one);
// nums[3]; // 索引越界会panic

读取vector中的元素, 方法2get方法:

1
2
3
4
5
6
7
8
match nums.get(1) {
    Some(value) => println!("{}", value),
    None => println!("none"),
}
match nums.get(3) {
    Some(value) => println!("{}", value),
    None => println!("none"),
}

遍历vector中元素:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// vector中添加元素
let mut nums :Vec<i32> = Vec::new();
nums.push(2);
nums.push(4);
nums.push(6);

// 遍历vector(可变方式)
for num in &mut nums {
    print!("{}, ", num);
    *num += 1;
}
println!();

// 遍历vector(不可变方式)
for num in &nums {
print!("{}, ", num);
}
println!();

使用枚举来储存多种类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];

2.2 字符串(String)

Rust的核心语言中只有一种字符串类型: str(字符串slice),它通常以被借用形式出现&str字符串slice它们是一些存储在别处的UTF-8编码字符串数据的引用。比如字符串字面值被存储在二进制输出中,字符串slice也是如此。

String类型是由标准库提供,它没有被写进Rust核心语言部分,它是可增长的、可变的、有所有权的、UTF-8编码的字符串类型。 Rust中的字符串是作为字节集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。String是一个Vec<u8>的封装。

当我们提到Rust中的字符串时可能是 &str,也可能是String

新建字符串:

1
2
3
4
5
6
7
let mut s = String::new();
s.push_str("hello");
println!("{}", s);
let s = String::from("world");
println!("{}, {}", s, s.len()); // 5
let s = String::from("世界");
println!("{}, {}", s, s.len()); // 6

更新字符串:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let mut s = String::from("hello");
s.push_str(" world");
s.push('!');
println!("{}", s);

// 合并两个String
let s1 = String::from("hello");
let s2 = String::from("world");
let s3 = s1 + " " + &s2;
// s1的所有权移交给s3, 此时不能再使用s1
// println!("{}", s1) // value borrowed here after move
println!("{}", s3);

let s1 = String::from("hello");
let s2 = String::from("world");
let s3 = format!("{} {}", s1, s2);
println!("{}", s3);

遍历字符串:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let s = "你好";
// 遍历String - 按字符
for c in s.chars() {
    print!("{}, ", c);
}
println!();
// 遍历String - 按字节
for c in s.bytes() {
    print!("{}, ", c);
}
println!();

2.3 hash map

创建HashMap:

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


let mut h :HashMap<String, i32>= HashMap::new();
h.insert("k1".to_string(), 1);
h.insert("k2".to_string(), 2);

访问HashMap:

1
2
3
4
5
6
7
8
match h.get("k2") {
    Some(v) => println!("v = {}", v),
    _ => println!("none"),
}

if let Some(v) = h.get("k1") {
    println!("v = {}", v);
}

遍历:

1
2
3
for (k, v) in &h {
    println!("{}:{}", k, v)
}

常用操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
h.entry("k1".to_string()).or_insert(100); // k1键不存在时插入


let words_line = "go rust python java groovy scala python java groovy ";
let mut word_count_map :HashMap<String, i32> = HashMap::new();
for word in words_line.split_whitespace() {
    let count = word_count_map.entry(String::from(word)).or_insert(0);
    *count += 1;
}
for (k, v) in &word_count_map {
    println!("{}: {}", k, v);
}

3.错误处理

rust提供了很多特性来处理出现错误的情况。

rust语言将错误分为两个类别: 可恢复错误(recoverable)和不可恢复错误(unrecoverable):

  • 可恢复错误通常代表用户报告错误和重试操作是合理的情况,例如未找到文件。rust中使用枚举类型Result<T, E>来实现。
  • 不可恢复错误是bug的同义词,例如尝试访问超过数组结尾的位置。rust中通过panic!来实现

3.1 panic!与不可恢复错误

rust提供了一个panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后直接退出。

1
2
3
4
5
#[test]
#[should_panic(expected = "crash and burn")]
fn panic_showcase() {
    panic!("crash and burn")
}
1
2
3
4
5
6
#[test]
#[should_panic(expected = "index out of bounds: the len is 3 but the index is 3")]
fn panic_showcase_vector() {
    let mut v = vec![1, 2, 3];
    v[3] = 100
}

3.2 Result与可恢复的错误

Result<T, E>枚举的定义大致如下:

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

Ok(T)T代表成功时返回的结果,ErrE代表失败是返回的错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[test]
#[should_panic]
fn error_handling() {
    use std::fs::File;
    let _file = match File::open("notexits.txt") {
        Ok(file) => file,
        Err(error) => {
            panic!("fail to open file: {:?}", error);
        },
    };
}

如果只是需要在失败场景下直接panic的话, Result<T, E>提供了unwrapexpect来简写上面的Match匹配:

1
2
3
4
5
6
#[test]
#[should_panic]
fn error_handling() {
    use std::fs::File;
    let _file = File::open("notexits.txt").unwrap();
}
1
2
3
4
5
6
#[test]
#[should_panic]
fn error_handling4() {
    use std::fs::File;
    let _file = File::open("notexits.txt").expect("fail to open file");
}

3.3 传播错误

传播错误是指如果发生错误时,将其返回给调用者让其决定如何处理错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[test]
#[should_panic]
fn error_propagating() { // 错误传播
    use std::io;
    use std::io::Read;
    use std::fs::File;
    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),
        }
    }
    
    read_username_from_file().unwrap();

}

使用?运算符简写错误传播:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#[test]
#[should_panic]
fn error_propagating2() { // 错误传播 ?运算符简写
    use std::io;
    use std::io::Read;
    use std::fs::File;
    fn read_username_from_file() -> Result<String, io::Error> {
        let f = File::open("hello.txt")?;
        let mut s = String::new();
        f.read_to_string(&mut s)?
        Ok(s)
    }
    
    read_username_from_file().unwrap();

}

4.泛型、trait

泛型是具体类型或者其它属性的抽象替代,用于减少代码重复。 使用泛型并不会造成程序上性能的损失。Rust通过在编译时进行泛型代码的单态化(monomorphization)来保证效率。 单态化是通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。

4.1 在函数定义中使用泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 // 定义泛型函数
fn largest<T: PartialOrd + Copy>(list :&[T]) -> T {
    let mut largest = list[0];
    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }
    largest
}

let nums = vec![3, 15, 9, 18, 7, 11];
let largest_num = largest(&nums);
println!("largest_num={}", largest_num);
let nums = vec![3.2, 15.0, 9.0, 18.1, 7.0, 11.0];
let largest_num = largest(&nums);
println!("largest_num={}", largest_num);

4.2 在结构体和方法定义中使用泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn get_x(&self) -> &T {
        &self.x
    }
}

let p1 = Point {
    x: 10,
    y: 30,
};
println!("p1 = {:#?}, p1.x={}", p1, p1.get_x());

4.3 trait

trait用于定义与其它类型共享的功能,类似于其它语言中的接口

  • 可以通过trait以抽象的方式定义共享的行为
  • 可以使用trait bounds指定泛型是任何拥有特定行为的类型

参考