Rust 基础
语言特点
内存所有权
变量在离开作用域后就被自动释放
将 String 赋值给其它变量,意味着从栈上拷贝了它的指针、长度和容量,并没有复制指针指向的堆上数据。
Rust 的操作方式称为移动而不是浅拷贝。
对于只在栈上的数据进行拷贝。如果一个类型实现了 Copy trait,在旧的变量赋值给其它变量后仍然可用。
向函数传递值,会使变量进入函数的作用域,在离开作用域时占用的内存会被释放。
函数的返回值也会转移所有权,将变量的值移出给调用的函数。
引用与借用
将对象的引用作为参数,而不是获取值的所有权。
引用默认不可变,使用可变引用 &mut,对一个变量同时最多只能有一个可变引用;拥有不可变引用时不可创建可变引用。(重叠作用域)
切片类型
引用集合中一段连续的元素序列,而不用引用整个集合。
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
泛型
Traits
定义特定类型可能与其它类型共享的功能。
类似于接口。
pub fn notify(item1: impl Summary, item2: impl Summary) {}
pub fn notify<T: Summary>(item1: T, item2: T) {}
通过 where 简化
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
生命周期
数据比引用有更长的生命周期,则是有效的引用。
生命周期标注表示多个引用的泛型生命周期参数如何相互关系。
不满足约束的值将被借用检查器拒绝。
#![allow(unused)]
fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
}
// 返回值的生命周期必须与 x, y 一致
静态生命周期,能够存活于整个程序期间。
智能指针
Treating Smart Pointers Like Regular References with the Deref Trait
引用计数,允许数据有多个所有者,记录总共由多少个所有者,当没有所有者时清理数据。智能指针相比引用,拥有它们指向的数据。
智能指针实现了 Deref 和 Drop 。Deref 使实例表现像引用一样,Drop 可以自定义离开作用域时的代码。
Box<T>用于在堆上分配值Rc<T>引用计数类型,其数据可有多个所有者Ref<T>和RefMut<T>,只读引用和可变引用
Deref 允许重载解引用运算符 *
Drop 运行清理代码
Rc<T> 通过不可变引用,在程序的多个部分之间只读地共享数据。
use std::rc::Rc;
let a = Rc::new();
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("{}", Rc::strong_count(&a));
Rc::clone 只会增加引用计数,不会进行深拷贝。
RefCell<T> 能够在外部值被认为是不可变的情况下修改内部值
use std::cell::RefCell;
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessagenger {
fn new() -> MockMessenger {
MockMessenger { sent_messages: RefCell::new(vec![])}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.borrow_mut().push(String::from(message));
}
}
避免引用循环:将 Rc<T> 变为 Weak<T>
调用 Rc::downgrade 会得到 Weak<T> 类型的智能指针。
弱引用并不属于所有权关系,弱引用的循环会在强引用计数为 0 时被打断。
为了使用指向的值,调用 Weak<T> 的 upgrade 方法,返回 Option<Rc<T>>
异步
Rust Futures > Async Rust: What is a runtime? Here is how tokio works under the hood
Rust 并不提供用于执行 Futures 和 Streams 的上下文。如果不进行嵌入式开发,选择 tokio 即可。
异步的核心是 event loops 或称作 processors
每个 event-loop 有自己的任务队列,tokio 中的 processor 如果任务队列为空可以从其它 processor 处抢夺。
tokio::spawn(async move{
for port in MOST_COMMON_PORTS_100 {
let _ = input_tx.send(*port).await;
}
});
可能导致阻塞的耗时任务 tokio::task::spawn_blocking
(后台的无限循环任务应该使用 Thread)
系统编程
测试
单元测试
使用 #[cfg(test)] 标注只在 cargo test 才编译运行测试代码
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
集成测试
tests/ 与 src/ 同级,其中每一个文件被当作独立的 crate 来编译
通过指定测试函数的名称作为 cargo test 的参数来运行特定集成测试
也可以指定运行 tests 目录下 integration_test.rs 中的所有测试
cargo test --test integration_test
闭包类型
- 闭包在被声明时,而非被调用时,会捕获外围词法作用域中的变量
- 闭包应延长所捕获变量的 lifetime,不能短于闭包自身的 lifetime
- 变量可通过
move,&mut和&的方式传入函数 -move将获得环境的所有权 -&mut将对环境造成可变引用 -&将对环境造成不可变引用 std::ops中定义了FnOnce,FnMut,Fn- 所有的Fn一定能被实现为FnMut,FnOnce- 所有的FnMut一定能实现为FnOnce
- Rust 的内置闭包只包含了环境,即 unboxed closure,因此闭包对象不需要携带函数指针
fn main() {
println!("{}", std::mem::size_of_val(&main)); // 0
let mut s = String::from("Hello");
let mut c = || s.push('!');
println!("{}", std::mem:size_of_val(&c)); // 8
}
- Rust 会尽可能选择最宽松的调用,即
Fn>FnMut>FnOnce - 使用
move关键字强制将所有捕获的变量移入闭包的环境 - 书写高阶函数时对闭包的约束
- 需要纯函数时,书写
Fn- 需要函数保存内部状态时,如伪随机数,书写FnMut- 类似创建线程的调用,选择FnOnce
pub fn spawn<F, T>(f: F) -> JoinHandle<T> where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
FnOnce
闭包获取了外部数据的所有权,且只可调用一次。数据在调用过程中被移动或释放。
struct MyClosure {
name: String
}
impl FnOnce for MyClosure {
fn call_once(self) {
drop(self.name)
}
}
FnMut
闭包获取了外部数据的可变引用。
闭包需要是 mut,因为需要获取自身的可变引用。
struct MyClosure {
i: &mut i32,
}
impl FnMut for MyClosure {
fn call_mut(&mut self) {
*self.i += 1;
}
}
Fn
闭包获取了外部数据的只读引用。
struct MyClosure {
i: &String,
}
impl Fn for MyClosure {
fn call(&self) {
println!("{}", self.msg);
}
}
move 关键字
move - Rust
Closures: Anonymous Functions that Capture Their Environment
在闭包内,将通过引用或可变引用获取的变量转化为由值获取的变量。
如果闭包内使用了数据的引用,但闭包与引用数据的生命周期不同,使用 move 来明确获取数据所有权。
fn create_closure() -> impl Fn() {
let msg = String::from("hello");
let v: Vec<i32> = vec![1, 2];
move || {
println!("{}", msg);
println!("{:?}", v);
}
}
struct MyClosure {
msg: String,
v: Vec<i32>,
}
impl Fn for MyClosure {
fn(&self) {
println!("{}", self.msg);
println!("{:?}", self.v);
}
}
[!EXAMPLE]
惰性求值,只在需要结果时执行闭包,并会缓存结果值struct Cacher<T> where T: Fn(u32) -> u32 { calculation: T, value: Option<u32>, } 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 }, } } } // 改用 HashMap 以支持不同的 arg