Rust 标准库¶
1 枚举¶
1.1 Option¶
`Option` 是一个枚举,和C++17中新增的`optional`功能一致,通常用在函数可能返回空值的情况。
Option
定义如下,其中 T
是泛型参数,Some(T)
表示该枚举成员的数据类型是 T
,换句话说,Some
可以包含任何类型的数据。
enum Option<T> {
Some(T),
None,
}
需要注意的几点如下:
- 无需使用 Option::
前缀就可直接使用 Some
和 None
。
- 当使用 None
是需要显示指定类型 T,如: Option<i8>
, 当使用 Some
会自动推导
- Option
值不能直接使用,需要 unwrap()
函数获取值。
#![allow(unused)]
fn main() {
let x: i8 = 5;
let y = Some(5);
let z:Option<i8> = None;
// unwrap()获取值,不能直接使用y
let sum = x + y.unwrap();
println!("{}",sum);
}
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
// 函数的返回值是一个选项
let result = divide(2.0, 3.0);
// 模式匹配以获取值
match result {
// 该划分有效
Some(x) => println!("Result: {x}"),
// 划分无效
None => println!("Cannot divide by 0"),
}
1.1.1 问号运算符,?
¶
与 Result
类型类似,在编写调用许多返回 Option
类型的函数的代码时,处理 Some
/None
可能会很乏味。问号运算符 ?
隐藏了一些在调用栈上传播值的样板。
它将替换为:
fn add_last_numbers(stack: &mut Vec<i32>) -> Option<i32> {
let a = stack.pop();
let b = stack.pop();
match (a, b) {
(Some(x), Some(y)) => Some(x + y),
_ => None,
}
}
有了这个:
fn add_last_numbers(stack: &mut Vec<i32>) -> Option<i32> {
Some(stack.pop()? + stack.pop()?)
}
好多了!
以 ?
结尾的表达式将导致 Some
的展开值,除非结果为 None
,在这种情况下,None
会从封闭的函数中提前返回。
?
可以用在返回 Option
的函数中,因为它提供了 None
的提前返回。
fn main() {
fn add_last_numbers(stack: &mut Vec<i32>) -> Option<i32> {
Some(stack.pop()? + stack.pop()?)
}
let mut v1: Vec<i32> = vec![1, 2, 3];
let mut result = add_last_numbers(&mut v1);
print!("First: ");
match result {
Some(x) => println!("value is {x}"),
None => println!("None"),
}
result = add_last_numbers(&mut v1);
print!("Second: ");
match result {
Some(x) => println!("value is {x}"),
None => println!("None"),
}
}
// 输出结果
// First: value is 5
// Second: None
1.2 Result¶
`Result`通常用于函数执行返回成功或者失败的场景。
Result
定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
Result 的一些转换方法
```rust#![allow(unused)]
fn main() {
let good_result: Result
// `is_ok` 和 `is_err` 方法按照他们说的做。
assert!(good_result.is_ok() && !good_result.is_err());
assert!(bad_result.is_err() && !bad_result.is_ok());
// `map` 消耗 `Result` 并产生另一个。
let good_result: Result<i32, i32> = good_result.map(|i| i + 1);
let bad_result: Result<i32, i32> = bad_result.map(|i| i - 1);
// 使用 `and_then` 继续计算。
let good_result: Result<bool, i32> = good_result.and_then(|i| Ok(i == 11));
// 使用 `or_else` 处理该错误。
let bad_result: Result<i32, i32> = bad_result.or_else(|i| Ok(i + 20));
// 消费结果并用 `unwrap` 返回内容。
println!("bad_result={}", bad_result.unwrap());
println!("good_result={}", good_result.unwrap());
}
// 输出结果 // bad_result=30 // good_result=true
## 2 类型
### 2.1 Any
### 2.2 String
## 3 智能指针
### 3.1 Box
```ad-info
Box 是一种从堆上分配内存的方式,相当于 C++中的智能指针,超过作用域自动释放分配的内存,也可以用`*`来解引用,还可以通过`.`来访问结构中的成员。
语法:
pub struct Box<T, A = Global>(_, _)
where
A: Allocator,
T: ?Sized;
示例:
#![allow(unused)]
fn main() {
let mut boxed_i32 = Box::new(5_i32);
// 解引用后赋值
*boxed_i32 = 3;
println!("{boxed_i32:?}");
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
let mut pt = Box::new(Point { x: 1, y: 2 });
// 修改结构体中的数据
pt.x = 2;
println!("{pt:?}");
}
// 输出结果
3
Point { x: 2, y: 2 }
3.1.1 解引用¶
fn main(){
let mut i=Box::new(2);
*i.as_mut()=3;
println!("{i}");
*i=4;
println!("{i}");
}
// 输出
// 3
// 4
3.1.2 配合 Any¶
#![allow(unused)]
fn main() {
use std::any::Any;
fn print_if_string(value: Box<dyn Any>) {
if let Ok(string) = value.downcast::<String>() {
println!("String ({}): {}", string.len(), string);
}
}
let my_string = "Hello World".to_string();
print_if_string(Box::new(my_string));
print_if_string(Box::new(0i8));
}
// 输出
// String (11): Hello World
3.1.3 into_raw、from_raw¶
- 功能:into_raw 从 Box 转化成裸指针,from_raw 从裸指针转换成 Box。 实现:
pub fn into_raw(b: Self) -> *mut T {
Self::into_raw_with_allocator(b).0
}
pub unsafe fn from_raw(raw: *mut T) -> Self {
unsafe { Self::from_raw_in(raw, Global) }
}
示例
#![allow(unused)]
fn main() {
let x: Box<String> = Box::new(String::from("Hello"));
let ptr: *mut String = Box::into_raw(x);
let x: Box<String> = unsafe { Box::from_raw(ptr) };
}
3.1.4 leak¶
- 功能:消耗并泄漏
Box
,返回一个可变引用,&'a mut T
。 请注意,类型T
必须超过所选的生命周期'a
。 如果类型仅具有静态引用,或者根本没有静态引用,则可以将其选择为'static
。
该函数主要用于在程序的剩余生命期内保留的数据。 丢弃返回的引用将导致内存泄漏。 如果这是不可接受的,则应首先将引用与 Box::from_raw
函数包装在一起,生成 Box
。
这个 Box
可以被丢弃,这将正确销毁 T
并释放分配的内存。
实现:
pub fn leak<'a>(b: Self) -> &'a mut T
where
A: 'a,
{
unsafe { &mut *mem::ManuallyDrop::new(b).0.as_ptr() }
}
示例:
#![allow(unused)]
fn main() {
let x = Box::new(41);
let static_ref: &'static mut usize = Box::leak(x);
*static_ref += 1;
assert_eq!(*static_ref, 42);
// Box::leak已经move了所有权,不能再访问x了
// let y=*x;
}
3.2 Rc¶
https://www.rustwiki.org.cn/zh-CN/std/rc/index.html
Rust中Rc相当于C++中shared_ptr,Weak相当于C++中的weak_ptr。
单线程引用计数指针。
特点如下:
- Rc<T>
类型提供了在堆中分配的 T
类型值的共享所有权。在 Rc
上调用 clone
会产生一个指向堆中相同分配的新指针。当指向给定分配的最后一个 Rc
指针被销毁时,存储在该分配中的值 (通常称为 “内部值”) 也将被丢弃。
- 默认情况下,Rust 中的共享引用不允许可变,Rc
也不例外:您通常无法获得 Rc
内部内容的可变引用。 如果需要可变性,则将 Cell
或 RefCell
放在 Rc
内;
- Rc
使用非原子引用计数。 如果需要多线程的原子引用计数,请使用 sync::Arc
。
- downgrade
方法可用于创建非所有者 Weak
指针。 Weak
指针可以被 upgrade
到 Rc
,但是如果已经丢弃了分配中存储的值,则它将返回 None
。 换句话说,Weak
指针不会使分配内部的值保持活动状态。但是,它们确实使分配 (内部值的后备存储) 保持活动状态。
- 为了避免 Rc
循环引用可以使用 Weak
用于中断循环。例如,一棵树可以具有从父节点到子节点的强 Rc
指针,以及从子节点到其父节点的 Weak
指针。
- Rc<T>
自动取消对 T
的引用 (通过 Deref
trait),因此您可以在类型为 Rc<T>
的值上调用 T
的方法。 为了避免与 T 方法的名称冲突,Rc<T>
本身的方法是关联函数,使用 完全限定语法 进行调用:
use std::rc::Rc;
let my_rc = Rc::new(());
let my_weak = Rc::downgrade(&my_rc);
RC<T>
也可以使用完全限定语法来调用 traits 的 Clone 等实现。有些人喜欢使用完全限定的语法,而另一些人则喜欢使用方法调用语法。
use std::rc::Rc;
let rc = Rc::new(());
// 方法调用语法
let rc2 = rc.clone();
// 完全限定的语法
let rc3 = Rc::clone(&rc);
3.2.1 Rc¶
单线程引用计数指针。Rc
代表引用计数。
实现:
pub struct Rc<T>
where
T: ?Sized,
{ /* private fields */ }
示例
use std::rc::Rc;
struct Owner {
name: String,
// ...其他字段
}
struct Gadget {
id: i32,
owner: Rc<Owner>,
// ...其他字段
}
fn main() {
// 创建一个引用计数的 `Owner`。
let gadget_owner: Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
}
);
// 创建属于 `gadget_owner` 的 `Gadget`。
// 克隆 `Rc<Owner>` 为我们提供了指向同一个 `Owner` 分配的新指针,从而增加了该进程中的引用计数。
//
let gadget1 = Gadget {
id: 1,
owner: Rc::clone(&gadget_owner),
};
let gadget2 = Gadget {
id: 2,
owner: Rc::clone(&gadget_owner),
};
// 处理我们的局部变量 `gadget_owner`。
drop(gadget_owner);
// 尽管丢弃了 `gadget_owner`,我们仍然可以打印出 `Gadget` 的 `Owner` 的名称。
// 这是因为我们只删除了一个 `Rc<Owner>`,而不是它指向的 `Owner`。
// 只要还有其他 `Rc<Owner>` 指向相同的 `Owner` 分配,它将保持活动状态。
// 字段投影 `gadget1.owner.name` 之所以起作用,是因为 `Rc<Owner>` 自动取消了对 `Owner` 的引用。
//
//
println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
// 在该函数的末尾,`gadget1` 和 `gadget2` 被销毁,并且它们与我们的 `Owner` 一起被算作最后引用。
// `Gadget` 现在也被摧毁。
//
}
3.2.2 Weak¶
实现:
pub struct Weak<T>
where
T: ?Sized,
{ /* private fields */ }
Weak
是Rc
的一个版本,它持有对托管分配的非所有权引用。通过在Weak
指针上调用upgrade
来访问分配,它返回一个Option<Rc<T>>
。- 由于
Weak
引用不计入所有权,因此它不会防止存储在分配中的值被丢弃,并且Weak
本身不保证该值仍然存在。 - 因此,当
upgrade
时,它可能返回None
。 但是请注意,Weak
引用 确实 会阻止分配本身 (后备存储) 被释放。 Weak
指针可用于保持对Rc
管理的分配的临时引用,而又不会阻止其内部值被丢弃。 它也用于防止Rc
指针之间的循环引用,因为相互拥有引用将永远不允许丢弃Rc
。 例如,一棵树可以具有从父节点到子节点的强Rc指针,以及从子节点到其父节点的
Weak` 指针。- 获取
Weak
指针的典型方法是调用Rc::downgrade
。 Weak<T>
不会自动解引用到T
,因为内部值可能已经被丢弃了。
示例:
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
struct Owner {
name: String,
gadgets: RefCell<Vec<Weak<Gadget>>>,
// ...其他字段
}
struct Gadget {
id: i32,
owner: Rc<Owner>,
// ...其他字段
}
fn main() {
// 创建一个引用计数的 `Owner`。
// 请注意,我们已将 `Gadget` 的所有者的 vector 放在 `RefCell` 内,以便我们可以通过共享的引用对其进行可变。
//
let gadget_owner: Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
gadgets: RefCell::new(vec![]),
}
);
// 如前所述,创建属于 `gadget_owner` 的 `Gadget`。
let gadget1 = Rc::new(
Gadget {
id: 1,
owner: Rc::clone(&gadget_owner),
}
);
let gadget2 = Rc::new(
Gadget {
id: 2,
owner: Rc::clone(&gadget_owner),
}
);
// 将 `Gadget` 添加到其 `Owner` 中。
{
let mut gadgets = gadget_owner.gadgets.borrow_mut();
gadgets.push(Rc::downgrade(&gadget1));
gadgets.push(Rc::downgrade(&gadget2));
// `RefCell` 动态借用到此结束。
}
// 遍历我们的 `Gadget`,将其详细信息打印出来。
for gadget_weak in gadget_owner.gadgets.borrow().iter() {
// `gadget_weak` 是一个 `Weak<Gadget>`。
// 由于 `Weak` 指针不能保证分配仍然存在,因此我们需要调用 `upgrade`,它返回 `Option<Rc<Gadget>>`。
//
//
// 在这种情况下,我们知道分配仍然存在,因此我们只用 `unwrap` 和 `Option`。
// 在更复杂的程序中,可能需要适当的错误处理才能获得 `None` 结果。
//
let gadget = gadget_weak.upgrade().unwrap();
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
// 在该函数的末尾,`gadget_owner`,`gadget1` 和 `gadget2` 被销毁。
// 现在没有指向该 `Gadget` 的强大 (`Rc`) 指针,因此它们已被销毁。
// 这会使 Gadget Man 的引用计数为零,因此他也被销毁了。
//
}
3.3 Cell & RefCell & OnceCell¶
https://www.rustwiki.org.cn/zh-CN/std/cell/index.html# https://rustwiki.org/zh-CN/book/ch15-05-interior-mutability.html 可共享的可变容器。
Rust 内存安全基于以下规则:给定一个对象 T
,它只能具有以下之一:
- 对对象具有多个不可变引用 (
&T
) (也称为别名)。 - 对对象有一个可变引用 (
&mut T
) (也称为可变性)。
这由 Rust 编译器强制执行。但是,在某些情况下,此规则不够灵活。有时需要对一个对象进行多次引用,然后对其进行可变的。
存在共享的可变容器以允许以受控的方式进行可变性,即使在出现混叠的情况下也是如此。 Cell<T>
、RefCell<T>
和 OnceCell<T>
允许以单线程方式实现这一点,但它们不实现 Sync
。 (如果您需要在多个线程之间进行别名和可变的,Mutex<T>
、RwLock<T>
、OnceLock<T>
或 atomic
类型是执行此操作的正确数据结构)。
Cell<T>
、RefCell<T>
和 OnceCell<T>
类型的值可以通过共享引用 (即常见的 &T
类型) 进行可变,而大多数 Rust 类型只能通过唯一的 (&mut T
) 引用进行可变。 我们说这些 cell 类型提供 内部可变性
(通过 &T
可变),与表现出 继承可变性
(仅通过 &mut T
可变) 的典型 Rust 类型形成对比。
Cell 类型分为三种类型: Cell<T>
、RefCell<T>
和 OnceCell<T>
。每个都提供了一种不同的方式来提供安全的内部可变性。
3.3.1 Cell¶
Cell<T>
通过将值移入和移出 cell 来实现内部可变性。 也就是说,永远无法获取到内部值的 &mut T
,如果不将其替换为其他内容,则无法直接获取值本身。 这两条规则都确保永远不会有一个以上的引用指向内部值。 该类型提供了以下方法:
- 对于实现
Copy
的类型,get
方法通过复制它来检索当前内部值。 - 对于实现
Default
的类型,take
方法将当前内部值替换为Default::default()
,然后返回替换后的值。 - 所有类型都有:
replace
: 替换当前内部值并返回替换后的值。into_inner
: 此方法使用Cell<T>
并返回内部值。set
: 该方法替换内部值,丢弃替换后的值。
Cell<T>
通常用于更简单的类型,在这些类型中复制或移动值不会占用太多资源 (例如数字),并且在可能的情况下通常应优先于其他 cell 类型。 对于较大的非复制类型,RefCell
提供了一些优势。
3.3.2 RefCell¶
RefCell<T>
使用 Rust 的生命周期来实现 “dynamic borrowing”,这是一个可以临时、独占、可更改访问内部值的过程。 RefCell<T>
s 的引用在运行时被跟踪,这与 Rust 的原生引用类型不同,后者在编译时完全静态地被跟踪。
可以使用 borrow
获取对 RefCell
的内部值 (&T
) 的不可更改引用,使用 borrow_mut
可以获取不可更改引用 (&mut T
)。 当调用这些函数时,它们首先验证是否满足 Rust 的借用规则: 允许任意数量的不可改变借用或允许单个不可改变借用,但绝不能同时使用。 如果尝试违反这些规则的借用,线程将崩溃。
RefCell<T>
对应的 Sync
版本为 RwLock<T>
。
3.3.2.1 在运行时记录借用¶
当创建不可变和可变引用时,我们分别使用 &
和 &mut
语法。对于 RefCell<T>
来说,则是 borrow
和 borrow_mut
方法,这属于 RefCell<T>
安全 API 的一部分。borrow
方法返回 Ref<T>
类型的智能指针,borrow_mut
方法返回 RefMut
类型的智能指针。这两个类型都实现了 Deref
,所以可以当作常规引用对待。
RefCell<T>
记录当前有多少个活动的 Ref<T>
和 RefMut<T>
智能指针。每次调用 borrow
,RefCell<T>
将活动的不可变借用计数加一。当 Ref<T>
值离开作用域时,不可变借用计数减一。就像编译时借用规则一样,RefCell<T>
在任何时候只允许有多个不可变借用或一个可变借用。
如果我们尝试违反这些规则,相比引用时的编译时错误,RefCell<T>
的实现会在运行时出现 panic。
3.3.3 OnceCell¶
OnceCell<T>
在某种程度上是 Cell
和 RefCell
的混合体,适用于通常只需要设置一次的值。 这意味着无需移动或复制内部值 (与 Cell
不同) 也无需运行时检查 (与 RefCell
不同) 即可获得引用 &T
。 然而,它的值一旦设置也不能更新,除非您有一个可变引用到 OnceCell
。
OnceCell
提供了以下方法:
get
: 获取对内部值的引用set
: 如果未设置则设置内部值 (返回Result
)get_or_init
: 返回内部值,如果需要则初始化它get_mut
: 提供对内部值的可变引用,仅当您对 cell 本身具有可变引用时才可用。
OnceCell<T>
对应的 Sync
版本为 OnceLock<T>
。
4 时间 time¶
5 线程 thread¶
5.1 Arc¶
https://www.rustwiki.org.cn/zh-CN/std/sync/struct.Arc.html
线程安全的引用计数指针。Arc
代表原子引用计数。
实现:
pub struct Arc<T>
where
T: ?Sized,
{ /* private fields */ }
Rust 中的共享引用不允许可变的,Arc
也不例外:您通常无法获得 Arc
内部内容的可变引用。如果需要通过 Arc
进行可变的,请使用 Mutex
,RwLock
或 Atomic
类型之一。
在线程之间共享一些不可变数据:
use std::sync::Arc;
use std::thread;
let five = Arc::new(5);
for _ in 0..10 {
let five = Arc::clone(&five);
thread::spawn(move || {
println!("{five:?}");
});
}
共享可变的 AtomicUsize
:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
let val = Arc::new(AtomicUsize::new(5));
for _ in 0..10 {
let val = Arc::clone(&val);
thread::spawn(move || {
let v = val.fetch_add(1, Ordering::SeqCst);
println!("{v:?}");
});
}
6 内存 alloc¶
6.1 mem¶
https://rustwiki.org/zh-CN/std/alloc/index.html