闭包 & 迭代器
闭包(Closure)通常是指词法闭包,是一个持有外部环境变量的函数。外部环境是指闭包定义时所在的词法作用域。外部环境变量,在函数式编程范式中也被称为自由变量,是指并不是在闭包内定义的变量。将自由变量和自身绑定的函数就是闭包。
闭包的两大特性:
- 延迟执行。返回的闭包只有在需要调用的时候才会执行。
- 捕获环境变量。闭包会获取其定义时所在作用域中的自由变量,以供之后调用时使用。
Rust的闭包语法形式参考了Ruby语言的lambda表达式:
fn main(){
let add_op = |a:i32,b:i32| -> i32 {a+b};
let c = add_op(1,2);
println!("1+2={}",c);
}
闭包的语法都大差不差,rust,ruby,python等都差不多一个样,
rust闭包的具体实现方式可以看Rust编程之道里面有讲解,目前没用到闭包,这里就不做讲解了,看了不用也记不住。
智能指针
智能指针(Box, Rc, Arc, Cell, RefCell, Cow等等),对原始指针进行包装,并添加额外的语义。
Box 是最直接的智能指针——它将数据分配到堆上而非栈上,在栈上只留一个指向堆中数据的指针。Box 没有性能开销,在下面的情况下使用:
- 递归类型,如链表和树,必须使用Box去包装引用自身的字段以保证编译时能确定大小
- 数据太大,希望移动所有权时减少拷贝数据消耗
- 希望持有特定 trait 的值,无关它的实际类型(即不知晓它的大小) 作者:_YKI https://www.bilibili.com/read/cv31741541/ 出处:bilibili
Rc:提供在堆中分配的 T 类型值的共享所有权。在 Rc 上调用克隆方法会生成一个指向堆中相同地址的新指针。当指向给定地址的最后一个 Rc 指针被销毁时,存储在该地址中的值也会被删除。
IO
Rust官方对于IO实现了IO trait,大部分函数看名字就知道作用
pub trait Read {
// Required method
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
// Provided methods
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { ... }
//is_read_vectored目前还是nightly API
fn is_read_vectored(&self) -> bool { ... }
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { ... }
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { ... }
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> { ... }
fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where Self: Sized { ... }
fn bytes(self) -> Bytes<Self> //是指将 Read 按逐字节的方式转换成迭代器。迭代器为Result<u8>,这个Result也是std::io::Result<T>
where Self: Sized { ... }
fn chain<R: Read>(self, next: R) -> Chain<Self, R>
where Self: Sized { ... }
fn take(self, limit: u64) -> Take<Self>
where Self: Sized { ... }
}
pub trait Write {
// Required methods
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
// Provided methods
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { ... }
fn is_write_vectored(&self) -> bool { ... }
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> { ... }
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self
where Self: Sized { ... }
}
很多类型实现了标准 IO 特型:File、TcpStream、Vec、&[u8],注意返回类型是std::io::Result,不是std::Result
pub type Result<T> = Result<T, Error>;
使用IO的demo:
use std::io::prelude::*;
use std::fs::File;
fn main() -> std::io::Result<()> {
let data = b"some bytes";
let mut pos = 0;
let mut buffer = File::create("foo.txt")?;
while pos < data.len() {
let bytes_written = buffer.write(&data[pos..])?;
pos += bytes_written;
}
Ok(())
}
安全并发
Rust中的线程管理和线程同步工具相关库为:std::thread模块和std::sync模块。Rust中的线程是本地线程,每个线程都有自己的栈和本地状态。创建一个线程如下:
use std::thread;
fn main(){
let mut v = vec![]
for id in 0..5 {
let child = thread::spawn(move || {println!("in child:{}",id);});
//spawn是Rust中线程初始化的函数
//直接使用thread::spawn生成的线程,默认没有名称,并且其栈大小默认为2MB。
//这里使用move关键字来强行将捕获变量id的所有权转移到闭包中。
v.push(child);
}
println!("in main join before:");
for child in v {
child.join();
//等待child结束后再接着运行
//但是child之间并没有相互等待的关系,输出是乱序的
}
println!("int main joint after");
}
并发安全是Rust的一个卖点。Rust中内置了两个trait:std::marker::Send和std::marker::Sync,实现了Send的类型可以安全地在线程间传递所有权,即跨线程移动;实现了Sync的类型,可以安全地在线程间传递不可变借用,即跨线程共享。和Send/Sync相反的标记是!Send/!Sync,表示不能在线程间安全传递的类型。智能指针Rc实现了!Send/!Sync,因为Rc内部并不是原子操作,在线程间传递会导致技术不准确。Rust提供了线程安全版的Rc,即Arc,内部使用的是原子操作,可以在线程间安全传递。
这两个标记trait反映了Rust看待线程安全的哲学:多线程共享内存并非线程不安全问题所在,问题在于错误地共享数据。通过Send和Sync将类型贴上“标签”,由编译器来识别这些类型是否可以在多个线程之间移动或共享,从而做到在编译期就能发现线程不安全的问题。
《Rust编程之道》
至于为什么这两个trait就可以实现并发安全,这两个具体怎么用?Rust中的锁怎么写?这部分等到后面用到再仔细分析,目前没有写项目,学了也是纸上谈兵。
宏系统
在 Rust 中宏分为两大类:声明式宏( declarative macros ) macro_rules! 和三种过程宏( procedural macros ):
- #[derive],在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码,例如 Debug 特征
- 类属性宏(Attribute-like macro),用于为目标添加自定义的属性
- 类函数宏(Function-like macro),看上去就像是函数调用
声明宏就是与传统的编程语言中的宏的概念是差不多的,但过程宏是使用源代码作为输入参数,基于代码进行一系列操作后,再输出一段全新的代码。有点类似于Python中的装饰器一样。
macro_rules!用来声明一个声明宏,也可以叫生成规则宏,他可以定义代码生成规则,声明式宏允许我们写出类似 match 的代码。match 表达式是一个控制结构,其接收一个表达式,然后将表达式的结果与多个模式进行匹配,一旦匹配了某个模式,则该模式相关联的代码将被执行。语法格式
macro_rules! macro_name{
() => { //跟match的语法非常像
code;
};
}
案例:
#[macro_export] //注释将宏进行了导出,这样其它的包就可以将该宏引入到当前作用域中,然后才能使用。
macro_rules! vec {
($($x:expr),*) => ( { //这里的匹配规则有点像正则表达式一样,$x:expr表示可以匹配任何类型,*表示可以重复无数次
let mut temp_vec = Vec::new();
$(temp_vec.push($x);)*
temp_vec
} );
}
大部分人都是宏的使用者,很少编写,啥时候用到再学吧。
总结
Rust语言的卖点就是:安全,安全,还是安全。这个安全包括内存安全,并发安全。其实这两点Python都能做到,但是为什么还要推出Rust呢?性能是关键!
Python保证内存安全是使用GC垃圾回收机制,保证并发安全是使用GIL锁。这两个机制都会极大降低Python的运行速度【目前据说GIL锁在新版本的Python中已经可以移除了,但是Python的速度还是没法跟Rust比】。
Rust确保这两点安全的方法是:所有权、声明周期机制以及强大的类型系统。所有权系统和生命周期确保了一份数据在同一时刻只会被一个变量所拥有,有效避免了悬垂指针以及双重释放等内存问题。内存安全的Bug和并发安全的Bug产生的内在原因是相同的,都是因为内存的不正当访问而造成的。同样,利用装载了所有权的强大类型系统,Rust还解决了并发安全的问题。Rust编译器会通过静态检查分析,在编译期就检查出多线程并发代码中所有的数据竞争问题。
类型系统是指编程语言中对于类型的一套规则,并不是真的有个系统,类型系统的主要作用就是类型推导,类型检测,类型转换,Rust是类型安全的,即:在运行时不会出现类型错误。
Rust作为后起之秀,借鉴了很多语言的设计思想,Haskell,C/C++,Python,Ruby等等,像智能指针,模板特化等很多新的技术都有借鉴,这些也一定程度上确保了内存安全。
谈完Rust设计哲学,再谈一谈Rust的编程范式,不用说,基本上所有现代语言都是混合编程范式的,理论上Rust可以支持各种编程范式,但是哪种支持更完备,编程更方便,自然是不一样的。在Rust中,不管哪一种单一编程范式来进行编程都很难受,面向对象,函数式,泛型编程,都是这样的。对于面向对象,Rust中有struct、方法和trait来实现面向对象。对于函数式,Rust中有闭包以及一切皆类型的类型系统。对于泛型编程,Rust有泛型,有模板特化,const模板参数等支持。所以对于Rust项目,比较推荐的编程范式是:轻度面向对象+泛型编程,函数式编程做为边边角角的辅助使用。更多可以看一下这篇文章
至此Rust编程就已经完结了,泛型编程和宏系统的应用,Rust设计模式的实现,异步编程和数据并行编程,unsafe特性,与其他语言相互调用,等等等等,这些特性我建议用到再学,因为学完不用很快就忘了,没必要,有一些概念不清楚的,不要去百度,去看Rust圣经或者直接去看Rust标准库官方文档,写的非常明确。
下一步就是开始从0写操作系统,清华的操作系统训练营还有好几天才开营,先学起来,下一篇打算跟着Rust实战这本书写一个CPU模拟器。