闭包有点类似于匿名函数,写法如下
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
expensive_closure(intensity)
类型推断和标注
闭包不要求像 fn
函数那样在参数和返回值上注明类型
闭包通常短小,关联小范围的context(编译器能可靠的推断参数和返回值的类型)
闭包为每个参数和返回值推断一个具体类型(也可以具体标注)
注意:闭包的定义最终只会为参数/返回值推断出唯一的具体类型
// 类型推断
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); //string类型已经锁定进闭包,因而报错
// 手动标注类型
let expensive_closure = |num: u32| -> u32 {
// ...
};
当在多处调用函数时,可以利用struct封装闭包并缓存结果,达到需要结果才调用函数的效果,避免多次执行函数(记忆化或延迟计算)
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
所有的闭包和函数都实现了 trait Fn
、FnMut
或 FnOnce
中的一个。例如以下代码
fn trait
的Cacher
才会实现new方法Cacher
时传入一个函数或者闭包即可实现new方法// src/lib.rs
impl<T> Cacher<T>
where T: Fn(u32) -> u32
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
// src/main.rs
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_result = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});
// snip
}
闭包会捕获上下文中的变量(函数则不行),但是会产生额外的内存开销。捕获的方式有三种:
FnOnce
(所有闭包都实现) ,会取得所有权因而只能调用一次
FnMut
,获取可变的借用值
Fn
, 获取不可变的借用值
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
// n equal_to_x(z: i32) -> bool { z == x } // 报错
let y = 4;
assert!(equal_to_x(y));
}
move关键词,可以强制闭包获取其使用的环境值的所有权
let x = vec![1,2,3];
let equal_to_x = move |z| z == x; // 强制闭包获取其使用的环境值的所有权
println!("can't use x here: {:?}", x);
迭代器是 惰性的(lazy),便于对一个序列的项进行某些处理
let v1_iter = vec![1, 2, 3].iter();
for val in v1_iter {
println!("Got: {}", val);
}
迭代器实现了Iterator
trait
pub trait Iterator {
type Item; //关联类型
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
获取迭代器
iter
,方法生成一个不可变引用的迭代器into_iter
,获取所有权并返回拥有所有权的迭代器iter_mut
,迭代可变引用自定义迭代器的实现只需要 Iterator
trait,并实现next
方法
impl Iterator for Counter {
// 关联类型
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
消耗迭代器的方法(默认实现方法),例如
产生其他迭代器的方法
// io示例程序的迭代器链式执行
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(query)) // 编列迭代器元素返回bool类型,如果为true则包含在filter产生的迭代器中
.collect()
}
引用是最简单的指针,而智能指针
Deref
和 Drop
trait常用的智能指针
Box
,用于在heap内存上分配值Rc
,一个引用计数类型,其数据可以有多个所有者Ref
和 RefMut
,通过 RefCell
访问( RefCell
是一个在运行时而不是在编译时执行借用规则的类型)Box
类型使用Box来获得确定大小的递归类型
BoX是一个指针,rust知道它需要多少空间(指针的大小是恒定的),因为:
Deref trait
和Drop trait
Deref trait
实现Deref Trait
可以自定义解引用运算符*的行为,感觉就是运算符重载
Deref
,智能指针可以像常规引用一样来处理let x = 5;
// let y = &x;
let y = Box::new(x);
prinln!("{}",*y) // *(y.deref())
实现Deref trait
的要求
use std::ops::Deref;
struct MyBox<T>(T); //元组结构体,实际是个具名元组
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
函数和方法的隐式解引用转化(Deref Coercion
)
假设T类型实现了Deref trait
,Deref coercion
可以把T的引用转化为T经过Deref
之后生成的引用
当某类型的引用传递给函数或方法时,但是它的类型于定义的参数类型不匹配,Deref Coercion
会自动发生,将其转化为所需的参数类型
在编译时完成,灭有额外的性能开销
对于以上Mybox的示例程序
fn hello(name: &str){
println!("{}",name);
}
fn main(){
let m = MyBox::new(String::from("hello");
// 此时会发生解引用转化
// rest 会不断尝试调用deref将&MYBox引用转化为&str
hello(&m);
// hello(&(*m)[..]); // 如果不适用隐式解引用转换,会出现冗杂的写法
}
类似于Deref trait
,使用DerefMut
trait 用于重载可变引用的*运算符
以下三种情况时会进行解引用强制转换:
T: Deref
:从 &T
到 &U
。T: DerefMut
:从 &mut T
到 &mut U
。T: Deref
:从 &mut T
到 &U
。Drop trait
实现Drop trait
自定义当值离开作用域时发生的动作
任何类型都可以实现Drop trait
只要求实现drop方法即可,是预导入的
不允许手动调用实现的drop方法,但是可以调用标准库的std::mem::drop
函数(预导入)提前触发drop
Rc
引用计数智能指针一个值有多个所有者,为了支持多重所有权
referece couting
),并追踪所有到值的引用使用场景为,需要再heap上分配数据,并被程序的多个部分读取,但在编译时无法确定那个部分最后使用完这些数据。
Rc
只能用于单线程场景Rc::clone(&a)
函数,增加引用计数Rc::string_count(&a)
,获得引用计数Rc::weak_count(&a)
Rc
引用是不可变的use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
RefCell
内部可变模式内部可变模式interior mutability pattern
,使不可变类型暴露出可修改其内部值的api
RefCell
代表了持有数据的唯一所有权,对比Box
Box |
RefCell |
---|---|
编译阶段强制代码遵守借用规则 | 只会在运行时检查借用规则 |
否则出现错误 | 否则触发panic |
尽早暴露问题,没有运行时开销,默认行为和通常的最佳选择 | 问题暴露延后,性能损失,特定环境的内存安全问题(不可变环境中修改自身数据) |
由于编译器的保守型,可能拒绝一些实际上遵守借用规则的代码(编译器无法理解),如果开发者能够保证借用规则满足,则可以使用RefCell
RefCell
不可变,仍旧能够修改其中的值选择的依据
RefCell
会记录当前存在的活跃Ref
和RefMut
智能指针
Ref
的值离开作用域释放时,不可变借用计数-1RefMut
的值离开作用域释放时,可变借用计数-1维护如下的借用检查规则