这里简单介绍基本常用的语法。
泛型
大部分语言都有针对类型的代码复用能力,Rust 同样提供了泛型支持模板功能,包括了函数、结构体等都支持,同时在 Rust 中会通过 trait 限制特定类型。
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
println!("The largest number is {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("The largest char is {}", largest(&chars));
}
实际上述内容编译时会报错,主要是由于比较符号并非对所有类型都适用,这就需要通过 Trait 对类型进行约束,也就是说,相关的类型需要实现对应的接口才可以。
只需要把上述的第一行修改为 fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
即可,也就是类型需要支持比较以及复制 trait 才行,这也被称为特征区间 (trait bounds)。
如果约束的 trait 太多,还可以将其放到最后,如下。
fn largest<T>(list: &[T]) -> T
where T: PartialOrd + Copy,
{
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
还可以参考如下示例。
struct Rectangle<T> {
width: T,
height: T,
}
impl<T> Rectangle<T>
where
T: std::ops::Mul<Output = T> + Copy,
{
fn area(&self) -> T {
self.width * self.height
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("The area is {}", rect.area());
}
大部分情况无需显示指定模板类型,编译器会自动进行推导,不过有些场景需要手动指定,否则有 type annotations needed for xxx
,例如有个队列 Queue::<String>::with_capacity(5)
。
关联类型
关联类型 (Associated Type) 是泛型的一个子概念,跟 trait 绑定,指定的方法中可以使用,用来指定输出的类型,在实现是具体指定类型。其中最常见的是 Iterator 这个 trait 的实现,其定义方法如下。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
当然,也可以直接使用泛型,区别在于,使用关联类型时,只需要在实现时指定后续调用接口时无需每次再指定,而泛型则需要每次实现时都指定类型。
逆变 VS. 协变
一般是编程语言中类型系统 (尤其是泛型) 中的概念,用来描述父子类型使用过程中是否允许替换,例如 Rectangle
是 Shape
的子类型,那么有如下规则:
- 协变 Covariance 使用父类 (Shape) 的地方可以用子类 (Rectangle) 替换。
- 逆变 Contravariance 使用子类 (Rectangle) 的地方可以使用父类 (Shape) 替换。
- 不变 Invariance 两者不能相互替换。
在 Java 中有继承关系,其中:A) 函数如参是协变,返回值是逆变;B) 泛型支持协变 <? super T>
和逆变 <? extends T>
两种。通常,逆变是希望使用更加具体的子类型。
而 Rust 没有继承的概念,但是生命周期采用的是相同的方式,详细可以查看死灵书的内容。
反射
Rust 中通过 std::any::Any
来实现反射的能力,其定义如下,同时保留了其使用示例。
pub trait Any: 'static {
/// use std::any::{Any, TypeId};
///
/// fn is_string(s: &dyn Any) -> bool {
/// TypeId::of::<String>() == s.type_id()
/// }
///
/// assert_eq!(is_string(&0), false);
/// assert_eq!(is_string(&"cookie monster".to_string()), true);
#[stable(feature = "get_type_id", since = "1.34.0")]
fn type_id(&self) -> TypeId;
}
pub struct TypeId {
t: (u64, u64),
}
也就是通过 TypeId
来标识具体的类型,而所有的类型都实现了 Any
接口,其对应的值是编译器在编译阶段确定的。另外,其中的 'static
意味着返回类型是在编译阶段已经确定的,而其数据可以修改。
如下是一个简单示例。
use std::any::Any;
fn dump(val: &dyn Any) {
if let Some(v) = val.downcast_ref::<u32>() {
println!("u32 {:x}", v);
} else if let Some(v) = val.downcast_ref::<&str>() {
println!("str {}", v);
}
}
fn main() {
let val = "Hello World";
let ptr: &dyn Any = &val;
println!("{:?}", ptr.type_id());
assert!(ptr.is::<&str>());
dump(&val);
let val: u32 = 42;
dump(&val);
}
闭包
现在的多数语言都有闭包特性的支持,简单来说就是匿名函数,可以捕获调用者作用域中的值,这样原环境中的变量所有权就没了,完全由闭包控制,即使脱离了上下文仍然可以运行。当然,个别情况并非完全一致,但这是理解闭包的关键。
其语法很简单 |args| -> ret {codes}
,如果没有返回值或者代码只有一行,那么还可以进一步简化,而且还可以自动推断参数类型。另外,被捕获的原上下文变量被称为自由变量。
fn main() {
// 简单示例
let foobar1 = |var: i32| -> i32 {
println!("variable = {}", var);
var
};
foobar1(10);
// 没有返回值
let foobar2 = |var: i32| {
println!("variable = {}", var);
};
foobar2(20);
// 只有一行
let foobar3 = |var: i32| println!("variable = {}", var);
foobar3(30);
// 自动推导类型
let foobar4 = |var| println!("variable = {}", var);
foobar4(40);
}
涉及到闭包变量捕获的会稍微有些麻烦。
变量捕获
与闭包相关的 Trait 有三个,分别为:
Fn
不修改,捕获的是&T
类型,闭包可以重复执行多次。FnMut
会修改,捕获的是&mut T
类型,可以重复执行多次。FnOnce
变量所有权会转移,也就是move
到闭包,只能执行一次,而且外部不能使用。
move
关键字 move 的作用是将所引用变量的所有权转移到闭包内,通常用于闭包的生命周期大于所捕获变量的原生命周期。
fn main() {
// 实现了Copy则闭包使用的是副本
let num01 = 5;
let mut num02 = 5;
let mut func01 = move |x: i32| -> i32 { num02 += x + num01; num02 };
println!("num is {:?}, mut is {:?}", num01, num02); // 5 5
let ret01 = func01(3);
println!("num is {:?}, mut is {:?}, ret is {:?}", num01, num02, ret01); // 5 5 13
// 因为没有move关键字,虽然实现了Copy所有权仍然被占用,而且在释放所有权之前无法使用
let num03 = 5;
let mut num04 = 5;
let mut func02 = |x: i32| -> i32 { num04 += x + num03; num04 };
//println!("num is {:?}, mut is {:?}", num03, num04); // no ownership
let ret02 = func02(3);
println!("num is {:?}, mut is {:?}, ret is {:?}", num03, num04, ret02); // 5 13 13
// 未实现Copy则闭包会拥有所有权,无论是否存在move
let str01 = "Hello";
let mut str02 = "World".to_string();
let mut func01 = move |x: &str| -> &str { str02 = str01.to_owned() + x + str02; str02 };
//println!("str is {:?}, mut is {:?}", num01, num02); // 5 5
let ret03 = func01(" ");
println!("str is {:?}, mut is {:?}, ret is {:?}", str01, str02, ret03); // 5 5 13
}
- move 对实现 copy trait 的变量,闭包使用的是副本,原变量无影响;未实现 copy trait 则原变量所有权无,只是非 mut 变量因为只读,外部扔可以使用。
- 无 move 所有变量被捕获,同样,只是因为非 mut 变量因为是只读,外部扔可以使用;另外,如果包的声明周期较短,外部仍然可以使用。
简言之,闭包会捕获变量,有两个特殊地方:A) move + copy trait 捕获时会使用复制,这样原作用域仍然可以使用;B) mut 变量被捕获后原作用域不能使用,非 mut 变量因为只读两侧都可以使用。
函数编程
最常用的是迭代器,提供了零抽象能力,不会引入运行时开销,如下是常用的示例。
fn main() {
let v0 = vec![1, 2, 3];
for i in v0.iter() {
println!("{}", i);
}
let v1: Vec<i32> = v0.iter().map(|x| x * x).collect();
println!("{:?}", v1); // [1, 4, 9]
let v1: Vec<&i32> = v0.iter().filter(|x| *x % 2 == 0).collect();
println!("{:?}", v1); // [2]
let v1: i32 = v0.iter().fold(0, |a, x| a + x);
println!("{:?}", v1); // 6
let v2 = vec![4, 5, 6];
let v1: Vec<i32> = v0.iter().chain(v2.iter()).copied().collect();
println!("{:?}", v1); // [1, 2, 3, 4, 5, 6]
let v2 = vec!["Andy", "Tom", "Bob"];
for (key, val) in v2.iter().zip(v0.iter()) {
println!("{}={}", key, val); // Andy=1 Tome=2 Bob=3
}
let mut v3 = vec![1, 2, 3];
for i in v3.iter_mut() {
*i *= 2;
}
println!("{:?}", v3); // [2, 4, 6]
}
常用方法:
map
转换数据,通过闭包处理其中每个元素,然后返回新的含结果迭代器,原迭代器不变。filter
过滤数据,同样通过闭包处理每个元素,当闭包返回true
时对应元素包含在新的迭代器中。fold
聚合数据,通过初始值和闭包循环调用每个元素,最终生成单一最终值。chain
用于将两个迭代器链接在一起,会先便利第一然后第二个。zip
将两个迭代器同时进行遍历处理,每次同时返回对应的元素。copied()
复制所有的元素,可以将&T
转换为T
类型,其作用与map(|&x| x)
相同。
注意,闭包调用时,某些函数使用的是引用。如果要同时修改变量,那么就需要使用 iter_mut()
迭代。