rust2

1.trait

在trait中可以包含函数,常量,类型等。如下所示是一个包含函数的trait。

1
2
3
trait Shape {
fn area(&self) -> f64;
}

所有的trait中都有一个隐藏的类型Self,代表了当前实现了该trait的具体类型,在trait中定义的函数,被叫做关联函数。如果一个关联函数的第一个参数是Self类型并且名字叫self的话(这样的参数叫做接收者),那么这个关联函数叫做方法,方法通过变量实例加上小数点进行调用;如果一个关联函数第一个参数不是接收者的话,那么这个关联函数叫做静态函数,通过类型::函数名的形式来调用。

所以在Rust中,Self(类型)和self(变量实例名)都是保留关键字。

当然我们也可以显示给接收者指定类型,但是同时他们在rust中都具有简写的写法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
trait Shape {
fn method1(self: Self);
fn method2(self: &Self);
fn method3(self: &mut Self);
}
// 对于上面的情况,也可以简写成下面的形式:
trait Shape {
fn method1(self);
fn method2(&self);
fn method3(&mut self);
}

下面是让一个类实现一个trait的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trait Shape {
fn area(&self) -> f64;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

fn main() {
let c = Circle{ radius: 10f64 };
println!("area is {}", c.area());
}

可以为一个struct实现一个匿名trait;并不是只能在trait中声明函数,也可以定义函数的行为,实现他的struct便能够直接使用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Circle {
radius: f64,
}

impl Circle {
fn get_radius(&self) -> f64 {
self.radius
}
}

fn main() {
let c = Circle{ radius: 10f64 };
println!("c.radius is {}", c.get_radius());
}

静态方法,只要一个trait里面的关联函数的第一个参数不是接收器的话,那么就表明这个方法是静态函数,使用类型::方式调用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
struct T(i32);

impl T {
fn func(this: &Self) { // 由于第一个参数的名字不是self,尽管类型是Self
println!("value is {}", this.0);
}
}
fn main() {
let x = T(42);
T::func(&x);
}

Rust规定,在函数中进行参数传递,返回值传递等地方,都要求这个类型在编译阶段具有确定的大小。而trait本身既不是具体类型,也不是指针类型,它只是定义了一个针对类型的抽象的约束,不同的类型可以实现同一个trait,同时满足同一个trait的类型可能具有不同的大小,所以trait在编译期间是没有确定大小的,因此不能使用trait作为实例变量,参数,返回值。

在rust中,一个类可以实现多个trait,那么问题来了,如果实现的多个trait里面具有同名函数的话,那么调用的时候该怎么调用呢?对此rust提出了完全函数调用的概念,这使得函数调用变得直接与直观,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
trait Cook {
fn start(&self);
}

trait Wash {
fn start(&self);
}

struct Chef;

impl Cook for Chef {
fn start(&self) {
println!("Cook::start");
}
}

impl Wash for Chef {
fn start(&self) {
println!("Wash::start");
}
}

2.key-value映射表

Rust提供了两种key-value哈希映射表,分别是:

  • 1.HashMap<K, V>
  • 2.BTreeMap<K, V>

其中泛型V必须是在编译期已知大小的类型,hashmap和btreemap的区别就在于HashMap是无序的,有可能你每次访问一个hashmap对象,得到的key-value键值对顺序都是不同的;但是对于btreemap来说就不会出现这个问题,BTreeMap则是有序的。

用法示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::collections::BTreeMap;
use std::collections::HashMap;

fn main() {
let mut hmap = HashMap::new();
let mut bmap = BTreeMap::new();

hmap.insert(1, "v");
hmap.insert(3, "y");
hmap.insert(2, "d");

bmap.insert(2, "q");
bmap.insert(1, "g");
bmap.insert(3, "w");

println!("{:?}", hmap); // 每次打印都可能顺序不同
println!("{:?}", bmap); // 每次打印顺序都不会变化
}

3.Set类型

Rust提供了两种set,分别是HashSet和BTreeSet。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::collections::HashSet;
use std::collections::BTreeSet;

fn main() {
let mut hset = HashSet::new();
let mut bset = BTreeSet::new();

hset.insert("s");
hset.insert("q");
hset.insert("w");

bset.insert("c");
bset.insert("a");
bset.insert("b");

println!("{:?}", hset); // 无序
println!("{:?}", bset); // 输出的顺序一定是{"a", "b", "c"}
}

4.trait类型

先看一个使用例子:

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
struct Pig;
struct Duck;

trait Fly {
fn fly(&self) -> bool;
}

impl Fly for Pig {
fn fly(&self) -> bool {
return false;
}
}

impl Fly for Duck {
fn fly(&self) -> bool {
return true;
}
}

// 泛型T代表实现了Fly trait的类型
fn fly_static<T: Fly>(s: T) -> bool {
s.fly()
}
fn fly_dyn(s: &Fly) -> bool {
s.fly()
}

fn main() {
let pig = Pig;
// ::<Pig>这样的语法形式用于给泛型函数指定具体的类型
assert_eq!(fly_static::<Pig>(pig), false); // 1.1
let duck = Duck;
assert_eq!(fly_static::<Duck>(duck), true);// 1.2

assert_eq!(fly_dyn(&Pig), false); // 2.1
assert_eq!(fly_dyn(&Duck), true); // 2.2
}

Rust将通过trait将类型和行为明确的进行了区分,充分贯彻了组合优于继承和面向接口编程的编程思想。

对于上面1.1和1.2这种代码调用方式,在rust中被叫做静态分发。rust编译器会fly_static::(pig)形如这样的调用生成特殊化的代码。也就是说,尽管写法是泛型这种抽象形式,但是在编译阶段,这些代码就已经被展开为具体类型的代码了。

而对于上面2.1和2.2这种代码调用方式,在rust中被叫做动态分发。他会在运行的时候检查相应类型的方法,会带来一定的运行时开销,不过开销很小。

5.错误处理

举个例子如下所示:

1
2
3
4
5
6
fn main() {
let t: Result<i32, &str> = Ok(-3);
assert_eq!(t.is_ok(), true);
let e: Result<i32, &str> = Err("some error message");
assert_eq!(e.is_ok(), false);
}