这里简单介绍基本常用的语法。
泛型
大部分语言都有针对类型的代码复用能力,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>;
}
当然,也可以直接使用泛型,区别在于,使用关联类型时,只需要在实现时指定后续调用接口时无需每次再指定,而泛型则需要每次实现时都指定类型。
反射
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 变量因为只读两侧都可以使用。