1. Fn, FnMut, FnOnce
1.1. 讨论的范畴:闭包 or 通常函数
讨论这三个 trait 时,通常是关于闭包的。普通的 rust 函数被视为实现了以上所有三个trait。可以用下面这段代码证明:
fn apply_fn<T>(value: T, f: impl Fn(T) -> T) -> T {
f(value)
}
fn apply_fnmut<T>(value: T, mut f: impl FnMut(T) -> T) -> T {
f(value)
}
fn apply_fnonce<T>(value: T, f: impl FnOnce(T) -> T) -> T {
f(value)
}
fn square(num: i32) -> i32 {
num * num
}
fn main() {
let result = apply_fn(5, square);
let result = apply_fnmut(5, square);
let result = apply_fnonce(5, square);
println!("{}", result);
}
1.2. 三个 Fn 系列 trait 的区别
对于闭包来说,编译期会自动为他们实现合适的 Fn 系列 trait。分三种:
FnOnce约束最强,表示闭包只能被执行一次,通常是由于取得了捕获值的所有权,或者闭包本身会被消耗FnMut约束次强,表示闭包可以被多次调用,且持有捕获变量的可变引用。Fn约束最弱,表示闭包可以被重复调用,且持有捕获变量的不可变引用。
实际上,FnOnce 是 FnMut 的超集(supertrait),而 FnMut 又是 Fn 的超集。即可以用伪码理解:
trait FnOnce {}
trait FnMut: FnOnce {}
trait Fn: FnMut {}
因此,一个较弱的闭包可以被传入更强的约束中,例如一个只实现了 Fn 的闭包可以被作为参数传入要求是 FnOnce 的场合。从逻辑来说,这样也是完全成立的。
1.3. 关于 FnOnce 的补充:调用后被 move,以及闭包实现 Copy
Rust 编译器是怎么限制 FnOnce 只能调用一次的呢?在 rustbook 中译版对闭包的介绍章节中,有一个尝试重复调用的例子:
fn fn_once<F>(func: F)
where
F: FnOnce(usize) -> bool,
{
println!("{}", func(3));
println!("{}", func(4));
}
这段代码会报错:
error[E0382]: use of moved value: `func`
--> src/main.rs:118:20
|
113 | fn fn_once<F>(func: F)
| ---- move occurs because `func` has type `F`, which does not implement the `Copy` trait
...
117 | println!("{}", func(3));
| ------- `func` moved due to this call
118 | println!("{}", func(4));
| ^^^^ value used here after move
|
note: this value implements `FnOnce`, which causes it to be moved when called
--> src/main.rs:117:20
|
117 | println!("{}", func(3));
| ^^^^
help: consider further restricting this bound
|
115 | F: FnOnce(usize) -> bool + Copy,
| ++++++
根据报错信息可知,FnOnce 的闭包在调用后会被 move 掉,因此我们不再能调用它了。但是如果加上 Copy 的 trait bound,就能可以多次调用这个 func 。查询 GPT,得知如果一个闭包捕获的变量都是 Copy 的,那么它也是 Copy 。这两个性质都能体现出闭包本身就是一种 rust 类型,一个类型的变量被 move 掉以后自然不能再访问,而如果一个类型里拥有的引用都是 Copy 的,那么它自然也是 Copy 的。
1.4. 补充之二:闭包的底层原理
这部分资料来源于 Efficient Rust p14。闭包的实现原理是,编译期创建一个隐藏类,存放所有需要被闭包捕获的引用,然后将闭包的匿名函数体实现为此类的方法。
2. Send Sync
Send:可以安全在线程间传递Sync:可以安全在线程间共享(不可变借用)
常见的 !Send + !Sync:Rc、原始指针
常见的 !Sync:Cell、RefCell
有条件的 Send + Sync:Vec<T>、Box<T>、Mutex<T>,需要 T 是
3. Sized ?Sized
Sized:类型大小编译期可以确定
常见的 !Sized: str、[]、dyn SomeTrait
?Sized:取消默认的Sized限制,例如泛型内部默认需要 sized
主要和引用 or Box 结合起来用,因为 !Sized 加上引用以后都是可以确定大小的,此时需要摆脱限制 Sized。例如:
fn process<T: ?Sized>(value: &T) {}
fn process_box<T: ?Sized>(value: Box<T>) {}
4. Copy Clone
5. Sink Stream
Stream: 异步产生数据Sink: 异步接受数据
5.1 Stream
Stream 可以理解成一个异步的迭代器。
Iterator<Item = T> // 调用 next(),立刻得到下一个值
Stream<Item = T> // 下一个值可能还没准备好,需要等待
核心是 poll_next :
trait Stream {
type Item;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>>;
}
- 返回
Poll::Pending:还没准备好 - 返回
Poll::Ready(Some(Item)):有数据了 - 返回
Poll::Ready(None):流已经结束
5.2 Sink
核心有四个 Poll
poll_ready()
│
├── Pending:接收端满了,暂时不能发送
│
└── Ready(Ok)
│
▼
start_send(item)
│
▼
poll_flush()
Sink 一般都需要内部实现缓存。 poll_ready 标识缓冲是否还有空间,start_send 将数据写入缓存,poll_flush 将数据从缓存刷新到真正接收方。
6. Ref RefCell Cell
-
Cell&RefCell:内部可变性。Cell只提供整体的操作,可以通过set\get(仅限 Copy)读写内部值RefCell提供借用 + 可变借用的接口。对应接口返回的类型就是Ref和RefMut
Cell因其特性主要用在 Copy 的小数据上;RefCell用在其他
