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) { 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); }
|
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; } }
fn fly_static<T: Fly>(s: T) -> bool { s.fly() } fn fly_dyn(s: &Fly) -> bool { s.fly() }
fn main() { let pig = Pig; assert_eq!(fly_static::<Pig>(pig), false); let duck = Duck; assert_eq!(fly_static::<Duck>(duck), true);
assert_eq!(fly_dyn(&Pig), false); assert_eq!(fly_dyn(&Duck), true); }
|
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); }
|