标准库类型

rust标准库有许多自定义类型:

  • 可增长的 String(字符串),如: “hello world”
  • 可增长的向量(vector): [1, 2, 3]
  • 选项类型(optional types): Option
  • 错误处理类型(error handling types): Result<i32, i32>
  • 堆分配的指针(heap allocated pointers): Box

箱子、栈和堆

  • 默认会在栈中分配,可以使用Box<T>将值装箱 ,就能够在堆上分配空间了。
  • Box是一个智能指针,指向堆分配的T类型的值,当离开时作用域,会自动调用析构函数,内部对象被销毁,堆上分配内存被释放。
  • 使用*进行解引用,会移除一层装箱(这项当于C语言中的指针)
  • box的宽度就是指针的宽度。
  • 使用mem::size_of_val(&T)可以进行地址大小检测。
  • (不知道和C语言中是否一样满足struct的补齐原则)

动态数组vector

  • vector和slice都是编译时大小未知,可以随时扩大或者缩小。
  • &[T]是Vec的全部或部分引用。
  • vector用三个值来描述:
    指向vec的指针
    vec的长度
    vec的容量(也就是要分配在堆之上的容量)
  • 使用push和pop进行添加和删除最后一个元素(命名方法和栈与堆的处理方法相同)
  • 注意for和iter结合使用。

字符串

  • string是由堆分配,可增长的,不是0结尾。
  • string被存储为由字节组成的vec
    &str是指向有效UTF-8序列的切片(&[u8]),可以用来查看String的内容。
  • 特殊的字符由反斜杠字符来转义,通常使用十六进制或者Unicode码位来表示。
  • 使用#可以在字符串中写#和",使用原始字符串。(raw String)

  • 使用字节串可以使用非UTF-8字符串或者是大部分文本。
  • 字节串没有实现Display,所以打印有些功能受限。
  • 可以使用单字节的转义字符,但是不能使用unicode转义字符。
  • 原始字节串和字符串写法相同,字节串转换成&str有可能失败。
  • 字节串如果不使用utf-8编码,在转换成&str会失败。

选项Option

  • Option中的Some(value)元组结构体,封装了T类型的值value。
  • None绑定到变量需要类型标注。
1
2
let none: Option<i32> = None;
let _equivalent_none = None::<i32>;
  • 解包使用unwrap,解包Some会取出其中的值,解包None会造成panic。

结果Result

  • 结果Result是Option的强化,它相对于Option,会指明为什么失败。
  • Result<T,E>:
    Ok(value):操作成功,返回value的值,value是具有T类型的值
    Err(why):操作失败,包装why(why能解释失败的原因,拥有E类型)

?运算符

  • ?运算符能够用在Result表达式之后,这会让result不用使用那么多的match表达式。

panic!

  • 使用panic!宏会产生panic恐慌,开始回退(unwind)栈,同时运行释放线程所拥有的资源(通过调用该线程中所有对象的析构函数)

散列表HashMap

  • 相较于vector通过整形下标来存储值,HashMap通过键(key)来存储值。使用HashMap键可以是布尔型、整型、字符串、或者是任意实现了Eq和Hash trait的其他类型(也就是说自定义类型要使用这个参数需要)
  • HashMap也是可以增长的,占据多余空间的同时会缩小自己。
  • 使用HashMap::with_capacity(unit)或者是HashMap::new()可以创建带有默认初始容量的HashMap或者是自定义容量的HashMap。
  • 使用(name).insert(“key”,12313)和(name).remove(&(“key”))进行设置。
  • 可以根据标签重新插入,使用(name).insert(“key”",12121)
  • hashmap有一个迭代器,会以任意顺序举出:
1
(&'a key, &'a value)

更改或者自定义关键字类型

  • 实现了EqHash trait的类型都可以充当HashMap的键,有:
    bool (当然这个用处不大,因为只有两个可能的键)
    int,unit,以及其他整数类型
    String 和 &str(友情提示:如果使用 String 作为键来创建 HashMap,则可以 将 &str 作为散列表的 .get() 方法的参数,以获取值)
  • 对于所有的集合类,如果他们包含的类型分别都实现了Eq和Hash,那么这些集合也都实现了Eq和Hash。例如如果T实现了Hash,则Vec也实现了。
  • 对于自定义类型,可以添加:
1
#[derive(PartialEq,Eq,Hash)]
  • 就可以轻松实现HashMap前提条件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
use std::collections::HashMap;

// Eq 要求你对此类型推导 PartiaEq。
#[derive(PartialEq, Eq, Hash)]
struct Account<'a>{
username: &'a str,
password: &'a str,
}

struct AccountInfo<'a>{
name: &'a str,
email: &'a str,
}

type Accounts<'a> = HashMap<Account<'a>, AccountInfo<'a>>;

fn try_logon<'a>(accounts: &Accounts<'a>,
username: &'a str, password: &'a str){
println!("Username: {}", username);
println!("Password: {}", password);
println!("Attempting logon...");

let logon = Account {
username: username,
password: password,
};

match accounts.get(&logon) {
Some(account_info) => {
println!("Successful logon!");
println!("Name: {}", account_info.name);
println!("Email: {}", account_info.email);
},
_ => println!("Login failed!"),
}
}

fn main(){
let mut accounts: Accounts = HashMap::new();

let account = Account {
username: "j.everyman",
password: "password123",
};

let account_info = AccountInfo {
name: "John Everyman",
email: "j.everyman@email.com",
};

accounts.insert(account, account_info);

try_logon(&accounts, "j.everyman", "psasword123");

try_logon(&accounts, "j.everyman", "password123");
}

散列集HashSet

  • HashSet是一个对HashMap<T,()>的封装。这样做的意义是只关心key而不关心其他值。

  • HashSet的优势是保证了不会出现重复数据。

  • 插入的值已经存在,那么新插入的值会取代旧插入的值。
    集合set可以做的事情:
    union(并集):获得两个集合中的所有元素(不含重复值)。

    difference(差集):获取属于第一个集合而不属于第二集合的所有元素。

    intersection(交集):获取同时属于两个集合的所有元素。

    symmetric_difference(对称差):获取所有只属于其中一个集合,而不同时属于 两个集合的所有元素。

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
use std::collections::HashSet;

fn main() {
let mut a: HashSet<i32> = vec!(1i32, 2, 3).into_iter().collect();
let mut b: HashSet<i32> = vec!(2i32, 3, 4).into_iter().collect();

assert!(a.insert(4));
assert!(a.contains(&4));

// 如果值已经存在,那么 `HashSet::insert()` 返回 false。
//assert!(b.insert(4), "Value 4 is already in set B!");
// 改正 ^ 将此行注释掉。

b.insert(5);

// 若一个集合(collection)的元素类型实现了 `Debug`,那么该集合也就实现了 `Debug`。
// 这通常将元素打印成这样的格式 `[elem1, elem2, ...]
println!("A: {:?}", a);
println!("B: {:?}", b);

// 乱序打印 [1, 2, 3, 4, 5]。
println!("Union: {:?}", a.union(&b).collect::<Vec<&i32>>());

// 这将会打印出 [1]
println!("Difference: {:?}", a.difference(&b).collect::<Vec<&i32>>());

// 乱序打印 [2, 3, 4]。
println!("Intersection: {:?}", a.intersection(&b).collect::<Vec<&i32>>());

// 打印 [1, 5]
println!("Symmetric Difference: {:?}",
a.symmetric_difference(&b).collect::<Vec<&i32>>());
}

引用计数Rc

  • 需要获取多个所有权的时候使用Rc(引用计数),Rc跟踪引用的数量相当于包裹在Rc值的所有者的数量。
  • Rc每多一个使用者,Rc值+1,当一个作用者被移出时,Rc值-1,当Rc值为0时,Rc和值都会被删除。
  • Rc克隆从不进行深拷贝,只创建一个指向包裹值的指针,并增加计数。
1
2
3
let rc: Rc<T> = Rc::new(T)//创建T类型的Rc引用计数
println!("rc 的引用计数数量: {}",Rc::strong_count(&rc));

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
use std::rc::Rc;

fn main() {
let rc_examples = "Rc examples".to_string();
{
println!("--- rc_a is created ---");

let rc_a: Rc<String> = Rc::new(rc_examples);
println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));

{
println!("--- rc_a is cloned to rc_b ---");

let rc_b: Rc<String> = Rc::clone(&rc_a);
println!("Reference Count of rc_b: {}", Rc::strong_count(&rc_b));
println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));

// 如果两者内部的值相等的话,则两个 `Rc` 相等。
println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b));

// 我们可以直接使用值的方法
println!("Length of the value inside rc_a: {}", rc_a.len());
println!("Value of rc_b: {}", rc_b);

println!("--- rc_b is dropped out of scope ---");
}

println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a));

println!("--- rc_a is dropped out of scope ---");
}

// 报错!`rc_examples` 已经移入 `rc_a`。
// 而且当 `rc_a` 被删时,`rc_examples` 也被一起删除。
// println!("rc_examples: {}", rc_examples);
// 试一试 ^ 注释掉此行代码
}

共享引用计数Arc

  • 通过Clone可以为内存堆中某个位置创建一个引用指针,同时增加引用计数器。
  • 由于在线程之间共享所有权,在指向某个值的最后一个引用指针退出作用域的时候该变量将被删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::sync::Arc;
use std::thread;

fn main() {
// 这个变量声明用来指定其值的地方。
let apple = Arc::new("the same apple");

for _ in 0..10 {
// 这里没有数值说明,因为它是一个指向内存堆中引用的指针。
let apple = Arc::clone(&apple);

thread::spawn(move || {
// 由于使用了Arc,线程可以使用分配在 `Arc` 变量指针位置的值来生成。
println!("{:?}", apple);
});
}
}