【注意】最后更新于 July 2, 2020,文中内容可能已过时,请谨慎使用。
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
代表成功时返回的结果,Err
的E
代表失败是返回的错误。
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>
提供了unwrap
和expect
来简写上面的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指定泛型是任何拥有特定行为的类型
参考