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:

1cargo new c10_package
2     Created binary (application) `c10_package` package
3
4tree c10_package
5c10_package
6├── Cargo.toml
7└── src
8    └── 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的库:

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

在lib.rs中创建模块:

 1pub mod front_of_house {
 2    pub mod hosting {
 3        pub fn add_to_waitlist() {}
 4
 5        pub fn seat_at_table() {}
 6    }
 7
 8    pub mod serving {
 9        pub fn take_order() {}
10
11        pub fn server_order() {}
12
13        pub fn take_payment() {}
14    }
15}
16pub fn eat_at_restaurant() {
17    // 绝对逻辑使用模块中的项
18    crate::front_of_house::hosting::add_to_waitlist();
19    // 相对路径使用模块中的项
20    front_of_house::hosting::add_to_waitlist();
21}

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

1use crate::front_of_house::hosting;
2use crate::front_of_house::hosting::add_to_waitlist as add_to;
3
4
5pub fn eat_at_restaurant() {
6    hosting::add_to_waitlist();
7    add_to();
8}

2.常见集合

2.1 vector

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

创建vector的方式:

1let mut nums :Vec<i32> = Vec::new();
2
3let nums = vec![1, 2, 3];

向vector中添加元素:

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

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

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

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

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

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

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

遍历vector中元素:

 1// vector中添加元素
 2let mut nums :Vec<i32> = Vec::new();
 3nums.push(2);
 4nums.push(4);
 5nums.push(6);
 6
 7// 遍历vector(可变方式)
 8for num in &mut nums {
 9    print!("{}, ", num);
10    *num += 1;
11}
12println!();
13
14// 遍历vector(不可变方式)
15for num in &nums {
16print!("{}, ", num);
17}
18println!();

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

 1enum SpreadsheetCell {
 2    Int(i32),
 3    Float(f64),
 4    Text(String),
 5}
 6
 7let row = vec![
 8    SpreadsheetCell::Int(3),
 9    SpreadsheetCell::Text(String::from("blue")),
10    SpreadsheetCell::Float(10.12),
11];

2.2 字符串(String)

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

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

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

新建字符串:

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

更新字符串:

 1let mut s = String::from("hello");
 2s.push_str(" world");
 3s.push('!');
 4println!("{}", s);
 5
 6// 合并两个String
 7let s1 = String::from("hello");
 8let s2 = String::from("world");
 9let s3 = s1 + " " + &s2;
10// s1的所有权移交给s3, 此时不能再使用s1
11// println!("{}", s1) // value borrowed here after move
12println!("{}", s3);
13
14let s1 = String::from("hello");
15let s2 = String::from("world");
16let s3 = format!("{} {}", s1, s2);
17println!("{}", s3);

遍历字符串:

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

2.3 hash map

创建HashMap:

1use std::collections::HashMap;
2
3
4let mut h :HashMap<String, i32>= HashMap::new();
5h.insert("k1".to_string(), 1);
6h.insert("k2".to_string(), 2);

访问HashMap:

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

遍历:

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

常用操作:

 1h.entry("k1".to_string()).or_insert(100); // k1键不存在时插入
 2
 3
 4let words_line = "go rust python java groovy scala python java groovy ";
 5let mut word_count_map :HashMap<String, i32> = HashMap::new();
 6for word in words_line.split_whitespace() {
 7    let count = word_count_map.entry(String::from(word)).or_insert(0);
 8    *count += 1;
 9}
10for (k, v) in &word_count_map {
11    println!("{}: {}", k, v);
12}

3.错误处理

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

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

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

3.1 panic!与不可恢复错误

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

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

3.2 Result与可恢复的错误

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

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

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

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

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

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

3.3 传播错误

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

 1#[test]
 2#[should_panic]
 3fn error_propagating() { // 错误传播
 4    use std::io;
 5    use std::io::Read;
 6    use std::fs::File;
 7    fn read_username_from_file() -> Result<String, io::Error> {
 8        let f = File::open("hello.txt");
 9        let mut f = match f {
10            Ok(file) => file,
11            Err(e) => return Err(e),
12        };
13        let mut s = String::new();
14        match f.read_to_string(&mut s) {
15            Ok(_) => Ok(s),
16            Err(e) => Err(e),
17        }
18    }
19    
20    read_username_from_file().unwrap();
21
22}

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

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

4.泛型、trait

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

4.1 在函数定义中使用泛型

 1 // 定义泛型函数
 2fn largest<T: PartialOrd + Copy>(list :&[T]) -> T {
 3    let mut largest = list[0];
 4    for &item in list.iter() {
 5        if item > largest {
 6            largest = item;
 7        }
 8    }
 9    largest
10}
11
12let nums = vec![3, 15, 9, 18, 7, 11];
13let largest_num = largest(&nums);
14println!("largest_num={}", largest_num);
15let nums = vec![3.2, 15.0, 9.0, 18.1, 7.0, 11.0];
16let largest_num = largest(&nums);
17println!("largest_num={}", largest_num);

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

 1#[derive(Debug)]
 2struct Point<T> {
 3    x: T,
 4    y: T,
 5}
 6
 7impl<T> Point<T> {
 8    fn get_x(&self) -> &T {
 9        &self.x
10    }
11}
12
13let p1 = Point {
14    x: 10,
15    y: 30,
16};
17println!("p1 = {:#?}, p1.x={}", p1, p1.get_x());

4.3 trait

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

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

参考