跳过正文

rust-std

··32090 字
Rust Rust-Crate
目录
rust-crate - 这篇文章属于一个选集。

1 std::alloc
#

标准库使用一个 global 内存分配器来为 Box<T>/Vec<T> 等分配堆内存。

  1. 默认情况下,使用 std::alloc::System 作为 global 内存分配器,System 同时实现 了 Allocator 和 GlobalAlloc trait。
    • unix/linux 系统,使用 malloc 实现。
    • windows 系统,使用 HeapAlloc 实现。
  2. 用户程序也可以使用 #[global_allocator] 来为程序指定一个实现 std::alloc::GlobalAlloc trait 的自定义的 global 内存分配器;

// 使用 OS 缺省的 System 内存分配器实现自定义内存分配器
use std::alloc::{GlobalAlloc, System, Layout};
struct MyAllocator;
unsafe impl GlobalAlloc for MyAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        System.alloc(layout)
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout)
    }
}
#[global_allocator]
static GLOBAL: MyAllocator = MyAllocator;
fn main() {
    // This `Vec` will allocate memory through `GLOBAL` above
    let mut v = Vec::new();
    v.push(1);
}

// 使用 jemalloc 内存分配器
use jemallocator::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc; // Jemalloc 是一个无 field 的 struct 类型。

fn main() {}


// 默认的 System 内存分配器
use std::alloc::System;
#[global_allocator]
static A: System = System;

fn main() {
    let a = Box::new(4); // Allocates from the system allocator.
    println!("{a}");
}

std::alloc::Allocator trait 定义了内存分配的接口,它根据传入的 std::alloc::Layout 对象信息来分配内存, 如要分配内存的 align 要求和 size。

pub unsafe trait Allocator {
    // Required methods
    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
    //...
}

pub struct Layout { /* private fields */ }

impl Layout
// 创建 T 类型的 Layout
pub const fn new<T>() -> Layout
// 从 T 的 value 创建 Layout
pub fn for_value<T>(t: &T) -> Layout where T: ?Sized
pub unsafe fn for_value_raw<T>(t: *const T) -> Layout where T: ?Sized

pub const fn from_size_align(size: usize, align: usize ) -> Result<Layout, LayoutError>
pub const unsafe fn from_size_align_unchecked( size: usize, align: usize ) -> Layout

// 获得 Layout 的 size 和 align 信息
pub const fn size(&self) -> usize
pub const fn align(&self) -> usize

pub fn dangling(&self) -> NonNull<u8>
pub fn align_to(&self, align: usize) -> Result<Layout, LayoutError>
pub fn padding_needed_for(&self, align: usize) -> usize
pub fn pad_to_align(&self) -> Layout
pub fn repeat(&self, n: usize) -> Result<(Layout, usize), LayoutError>
pub fn extend(&self, next: Layout) -> Result<(Layout, usize), LayoutError>
pub fn repeat_packed(&self, n: usize) -> Result<Layout, LayoutError>
pub fn extend_packed(&self, next: Layout) -> Result<Layout, LayoutError>
pub fn array<T>(n: usize) -> Result<Layout, LayoutError>

内存分配函数(返回裸指针,如 *mut u8 类型):

  • alloc : Allocate memory with the global allocator.
  • alloc_zeroed : Allocate zero-initialized memory with the global allocator.
  • dealloc : Deallocate memory with the global allocator.
  • handle_alloc_error : Signal a memory allocation error.
  • realloc : Reallocate memory with the global allocator.
  • set_alloc_error_hook : Registers a custom allocation error hook, replacing any that was previously registered.
  • take_alloc_error_hook : Unregisters the current allocation error hook, returning it.

这些函数都使用默认的 Global 分配器,这些函数都没有返回错误,需要根据返回的 raw pointer 是否为 null 来判断是否分配成功。

  • handle_alloc_error(layout) 可以用来处理分配错误情况,默认的行为是在 stderr 打印消息, abort进程
  • set_alloc_error_hook() 和 take_alloc_error_hook() 可以设置调用 handle_alloc_error() 时的行为,可以选择 panic 或 abort;
// Allocate memory with the global allocator.
pub unsafe fn alloc(layout: Layout) -> *mut u8

use std::alloc::{alloc, dealloc, handle_alloc_error, Layout};
unsafe {
    let layout = Layout::new::<u16>();
    let ptr = alloc(layout); // ptr 是 *mut u8 类型
    if ptr.is_null() {
        handle_alloc_error(layout);
    }

    *(ptr as *mut u16) = 42;
    assert_eq!(*(ptr as *mut u16), 42);

    dealloc(ptr, layout);
}

// Allocate zero-initialized memory with the global allocator.
pub unsafe fn alloc_zeroed(layout: Layout) -> *mut u8

use std::alloc::{alloc_zeroed, dealloc, Layout};

unsafe {
    let layout = Layout::new::<u16>();
    let ptr = alloc_zeroed(layout);

    assert_eq!(*(ptr as *mut u16), 0);

    dealloc(ptr, layout);
}

// Deallocate memory with the global allocator.
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout)

// Reallocate memory with the global allocator.
pub unsafe fn realloc(ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8

// Registers a custom allocation error hook, replacing any that was previously registered.
pub fn set_alloc_error_hook(hook: fn(_: Layout))
#![feature(alloc_error_hook)]

use std::alloc::{Layout, set_alloc_error_hook};

fn custom_alloc_error_hook(layout: Layout) {
    panic!("memory allocation of {} bytes failed", layout.size());
}

set_alloc_error_hook(custom_alloc_error_hook);

2 std::any
#

std::any module 提供了返回类型或值名称的函数(即 type 反射):

  1. type_name::<T>() : 返回 T 的类型名称字符串;
  2. type_name_of_value(&T) : 返回指向的 T 的值(必须传入 T 的借用)的类型名称字符串;
assert_eq!(std::any::type_name::<Option<String>>(), "core::option::Option<alloc::string::String>",);

use std::any::type_name_of_val;
let s = "foo";
let x: i32 = 1;
let y: f32 = 1.0;
assert!(type_name_of_val(&s).contains("str"));
assert!(type_name_of_val(&x).contains("i32"));
assert!(type_name_of_val(&y).contains("f32"));

// std::any::type_name_of_val() 返回传入的引用值的类型,参数类型是 &val, 返回 val 的类型。如果传入
// 的是引用类型值 val,则返回的是 *val 的类型。
println!("type name: {}", std::any::type_name_of_val(&val)); // type name: &dyn core::any::Any

std::any module 也提供了 std::any::Any trait ,Rust 为 绝大部分类型实现了该 trait 。所以对象借用可以转换为 &dyn Any,对象自身可以转换为 Box<dyn Any>。但是对于引用类型,如果不是 'static 类型则没有实现 Any trait。

// Any trait 需要满足 'static 限制
pub trait Any: 'static {
    fn type_id(&self) -> TypeId;
}

use std::any::{Any, TypeId};
fn is_string(s: &dyn Any) -> bool {
    TypeId::of::<String>() == s.type_id()
}

// 传入的是 'static 引用,自动实现了 Any trait
assert_eq!(is_string(&0), false);
assert_eq!(is_string(&"cookie monster".to_string()), true);

Any trait 的 type_id() 方法返回一个 TypeId 对象,表示该 trait object 的具体类型,可以用来比较/Hash 和显示:

// of() 函数返回类型的 TypeId
use std::any::{Any, TypeId};

fn is_string<T: ?Sized + Any>(_s: &T) -> bool {
    TypeId::of::<String>() == TypeId::of::<T>()
}

assert_eq!(is_string(&0), false);
assert_eq!(is_string(&"cookie monster".to_string()), true);


use std::any::{Any, TypeId};

let boxed: Box<dyn Any> = Box::new(3_i32);

// You're more likely to want this:
let actual_id = (&*boxed).type_id();
// ... than this:
let boxed_id = boxed.type_id();

assert_eq!(actual_id, TypeId::of::<i32>());
assert_eq!(boxed_id, TypeId::of::<Box<dyn Any>>());

&dyn Any 实现了 is<T>()/downcast_ref<T>()/downcast_mut<T>() 方法,可以对 trait object 进行动态类型判断和处理:

  • T 必须是具体类型,而不能是 trait 类型。
use std::any::Any;

fn is_string(s: &dyn Any) {
    if s.is::<String>() {
        println!("It's a string!");
    } else {
        println!("Not a string...");
    }
}
is_string(&0);
is_string(&"cookie monster".to_string());

fn modify_if_u32(s: &mut dyn Any) {
    if let Some(num) = s.downcast_mut::<u32>() {
        *num = 42;
    }
}
let mut x = 10u32;
let mut s = "starlord".to_string();
modify_if_u32(&mut x);
modify_if_u32(&mut s);
assert_eq!(x, 42);
assert_eq!(&s, "starlord");

三个 Box 类型的 trait object 都实现了 downcast<T> 和 downcast_unchecked<T> 方法:

  • impl<A> Box<dyn Any, A>
  • impl<A> Box<dyn Any + Send, A>
  • impl<A> Box<dyn Any + Sync + Send, A>
pub fn downcast<T>(self) -> Result<Box<T, A>, Box<dyn Any, A>> where T: Any
pub unsafe fn downcast_unchecked<T>(self) -> Box<T, A> where T: Any

// 例子
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));


#![feature(downcast_unchecked)]
use std::any::Any;
let x: Box<dyn Any> = Box::new(1_usize);
unsafe {
    assert_eq!(*x.downcast_unchecked::<usize>(), 1);
}

3 std::backtrace
#

捕获和打印 thread 的 stack backtrace。

程序二进制需要包含 debug information,否则 backtrace 不显示 filename/line number。

  • Rust 1.77.0 版本开始,默认构建的 release 类型二进制本默认开启了 strip, 故不包含 debug info

Backtrace::capture() 的行为受两个环境变量控制,所以为了 Backtrace::capture() 能返回结果,需要至少设置一个环境变量值为非 0。

  1. RUST_LIB_BACKTRACE - if this is set to 0 then Backtrace::capture will never capture a backtrace. Any other value set will enable Backtrace::capture.
  2. RUST_BACKTRACE - if RUST_LIB_BACKTRACE is not set, then this variable is consulted with the same rules of RUST_LIB_BACKTRACE.
  3. If neither of the above env vars are set, then Backtrace::capture will be disabled.
impl Backtrace

pub fn capture() -> Backtrace // capture 受环境变量调控
pub fn force_capture() -> Backtrace // 强制打印
pub const fn disabled() -> Backtrace // 关闭
// 返回是否支持 Backtrace,如受环境变量和上面 disabled() 环境影响
pub fn status(&self) -> BacktraceStatus

impl<'a> Backtrace
pub fn frames(&'a self) -> &'a [BacktraceFrame] // BacktraceFrame 实现了 Debug,没有方法。

Backtrace::force_capture() 函数不参考上面两个环境变量的值,强制返回 Backtrace。

use std::backtrace::Backtrace
fn main() {
    let bt  = Backtrace::force_capture();
    println!("{}", bt); // Backtrace 实现了 Debug 和 Display,可以直接打印显示
}

4 std::boxed
#

std::boxed module 提供了堆分配的 Box<T> 类型。

Box 拥有分配内存的所有权,当 Box 离开 scope 时 drop 对应的内存。

std::boxed::Box 默认使用 Global 内存分配器在堆上分配内存, 由于泛型参数 A 有默认值, 所以一般只需要指定 T 类型, 如 Box<u8>:

pub struct Box<T, A = Global>(/* private fields */)
where
    A: Allocator,
    T: ?Sized;

// 使用 Box 来将 stack 变量转换为 heap 变量:
let val: u8 = 5;
let boxed: Box<u8> = Box::new(val);

Box<T> 是智能指针类型, 实现了 Deref<Target=T> trait ,所以支持 解引用操作

let boxed: Box<u8> = Box::new(5);
let val: u8 = *boxed;

Box<T> 的大小是固定的,可以保存 trait object 对象 以及实现自引用类型(如树形递归数据结构):

// 使用 Box 保存递归数据类型对象
#[allow(dead_code)]
#[derive(Debug)]
enum List<T> {
    Cons(T, Box<List<T>>),
    Nil,
}
let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{list:?}");


// 使用 Box 保存 trait object
let _b: Box<dyn std::fmt::Display> = Box::new(123i32);
let _b: Box<&dyn std::fmt::Display> = Box::new(&123i32);

// 1i32 实现了 dyn std::fmt::Display 和 dyn std::any::Any, 所以 1i32 可以 unsized coercion 到
// trait object
//
// trait object 有两种形式
// 1. &dyn Trait
let dsd: &dyn std::fmt::Display = &1i32;
// 2. Box<dyn Trait>
let bo: Box<dyn std::fmt::Display> = Box::new(1i32);
let bo: Box<dyn std::any::Any> = Box::new(1i32);
let bo: Box<&dyn std::fmt::Display> = Box::new(&1i32);


// 以下也是 CoerceUnsized trait 实现的 type coercion 的转换, 将 T 指针转换为 U 指针, U 需要是
// unsized type.
//
// Rust 支持 Box<T> -> Box<U>, 只要 T 可以 CoerceUnsized 到 U
let b1: Box<i32> = Box::new(123i32);
let b2: Box<dyn std::fmt::Display> = b1;
let bo: Box<[i32]> = Box::new([1, 2, 3]); // [T; N] -> [T]
let b1 = Box::new(123i32); // i32 -> dyn std::fmt::Display
let b2: Box<dyn std::fmt::Display> = b1;

只要 T: Sized, 则 Box<T> 将确保可以用一个 single pointer 来代表, 而且和 C 指针(T*)是 ABI 兼容的 =。这意味着, =如果要在 C 中调用 Rust func ,可以在 Rust func 中使用 Box<T> 类型,而在 C side 当作 T*来使用。但是如果在 Rust func 中调用 C func,则不建议对 C 指针使用 Box<T> 类型,否则是未定义行为。而是尽可能使用 raw pointer;

例如,Rust 实现的,供 C 调用的函数签名 的 header:

/* C header */

/* Returns ownership to the caller */
struct Foo* foo_new(void);

/* Takes ownership from the caller; no-op when invoked with null */
void foo_delete(struct Foo*);

这两个 C 函数的 Rust 实现如下:

#[repr(C)]
pub struct Foo;

#[no_mangle]
pub extern "C" fn foo_new() -> Box<Foo> {
    Box::new(Foo)
}

// foo_delete() 的参数可能是 nullable 指针,而 Box<Foo> 不可能是 null,故需要使用 Option
#[no_mangle]
pub extern "C" fn foo_delete(_: Option<Box<Foo>>) {}

// 也可以从 raw pointer 创建 Box 对象
let value = &mut 32 as *mut i32;
let box = Box::<i32>::from_raw(value);
let value2 = &*box as *const i32;

Box<T> 在堆上为 T 分配内存:

  1. 封装编译时无固定大小的对象,解决自引用递归类型或 trait object 问题;
  2. 避免栈上的大量内存拷贝,如使用 Box 来将 struct/array 内容保存在堆上;
  3. 自动解引用和类型转换。

Box 是一个拥有 T 值的智能指针, 它实现了 Drop trait,从而自动释放堆内存:

Box<T> 默认没有实现 Copy,在赋值时会被移动。其它智能指针,如 Rc/Arc/Cell/RefCell 类似。

// 解决自引用递归类型 unsize 的问题
enum List {
    Cons(i32, Box<List>),
    Nil,
}
use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));
}

use std::mem;
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

#[allow(dead_code)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

fn origin() -> Point {
    Point { x: 0.0, y: 0.0 }
}

fn boxed_origin() -> Box<Point> {
    // 在堆上分配 Point
    Box::new(Point { x: 0.0, y: 0.0 })
}

enum List {
    Cons(i32, Box<List>), // 元组类型,由于 Box 没有实现 Copy,所以整个元组没有实现 Copy
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
    let b = Cons(3, Box::new(a)); // a 所有权转移。这里不能传入 &a, 否则只是分配一个指针的空间。
    let c = Cons(4, Box::new(a)); // 编译器报错,解决办法是使用 Rc<T>
}

Box<T> 是实现了 Deref<Target=T> 的智能指针:

  1. Box<T> 是一个指针,故支持 * 运算符来解引用,*v 等效于 *(v.deref()),返回 T 值 ,&*v 来返回 &T 值
  2. 在需要 &T 的地方可以直接使传入 &Box<T> 值;
fn hello(name: &str) {
    println!("Hello, {name}!");
}


fn main() {
    let x = 5;
    let y = Box::new(x);
    assert_eq!(5, *y); // *y 等效为 *(y.deref()), 返回 5。

    let boxed: Box<u8> = Box::new(5);
    let val: u8 = *boxed;

    let m = MyBox::new(String::from("Rust"));
    hello(&m); // Rust 自动进行多级 Defer 解引用,也就是 deref coercion:MyBox<String> ->  String -> str

    // 如果 rust 不做 deref coercion,则需要做如下繁琐操作
    hello(&(*m)[..]);
}

自定义对象实现 Deref:

struct MyBox<T>(T);

use std::ops::Deref;
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

5 std::cell
#

Rust 对象只能有一个所有者,对象的借用也需要满足借用规则,如不能通过共享借用 &T 来修改 T 的值。

std::cell module 提供了多种内部可变性类型, 这些 Cell 对象可以通过共享引用 &T 来进行修改:

  • Cell<T>: T 必须实现 Copy;
  • RefCell<T>: T 不需要实现 Copy;
  • OnceCell<T>: 对 T 只进行一次初始化。(不支持多线程,多线程版本是 OnceLock);
  • LazyCell<T>: 和 OnceCell 类似,但是通过 Deref 机制来自动初始化一次。(不支持多线程,多线程版本是 LazyLock);

各种 Cell 都没有实现 Sync,只能在单线程环境中使用。在多线程环境中, Mutex<T>, RwLock<T>, OnceLock<T> 和各种 automic type 实现了多线程的内部可变性。

在不可变对象中引入可变性,称为 内部可变性(interior mutability)

std::cell module 提供了 Cell<T> 和 RefCell<T> 来支持这种场景:在对 Cell 自身没有 &mut 的情况下,也能 get/set 它的 field。

Cell 适用于实现 Copy trait 的对象类型,它使用 Copy 来 get() 对象,set() 方法的参数是 &self 而非 &mut self,但是又能对 self 进行设置操作,这是 Cell<T> 在 Rust 中存在的主要价值。

  1. Cell::new(value):转移 value 对象的所有权,创建一个 Cell 对象;
  2. cell.get():使用对象的 Copy trait 来返回 value;
  3. cell.set(&self, value: T):将 value 保存到 Cell 中,drop 原来保存的值,注意参数是 &self 而非 &mut self;
use std::cell::Cell;

pub struct SpiderRobot {
    // ...
    hardware_error_count: Cell<u32>,
    // ...
}


// SpiderRobot 的非 &mut 方法也可以 get/set Cell<T> 对象。
impl SpiderRobot {
    // &self 而非 &mut self
    pub fn add_hardware_error(&self) {
        let n = self.hardware_error_count.get();
        // 先 Copy 旧对象,然后设置为传入的值,然后再 drop 旧对象。
        self.hardware_error_count.set(n + 1);
    }
    pub fn has_hardware_errors(&self) -> bool {
        self.hardware_error_count.get() > 0
    }
}

如果 T 类型没实现 Copy trait,则不能使用 Cell<T>,但可以使用 RefCell<T>, 后者使用借用来获得或修改对象。

  1. RefCell::new(value) : 创建一个 RefCell 对象,转移 value 的所有权;
  2. ref_cell.borrow():返回一个 Ref<T> 类型对象 ,即对内部保存值的共享借用。如果该值已经被 &mut 则会 panic;
  3. ref_cell.borrow_mut() :返回一个 RefMut<T> 类型对象, 即对内部保存值的可变借用。如果该值已经被共享借用则会 panic;
  4. ref_cell.try_borrow() 和 ref_cell.try_borrow_mut():两个方法都返回一个 Result 供检查,避免 panic;

borrow()/borrow_mut() 返回的 Ref<T>/RefMut<T> 是智能指针,实现了 Deref<Target=T>, 可以直接调用 T 的方法。

对于已经通过 r = RefCell<T>.borrow() 的 r 不能调用 borrow_mut(), 否则会导致运行时 panic,反之依然:

  • Rust 对引用和智能指针如 Box<T> 进行 编译时检查 ,避免引用出错。但是对于 RefCell<T>,Rust 会 在运行时 检查这些规则,在出现违反借用的情况下触发 panic 来提前终止程序。
use std::cell::RefCell;
let ref_cell: RefCell<String> =  RefCell::new("hello".to_string());

let r = ref_cell.borrow();
let count = r.len(); // ok, returns "hello".len()
assert_eq!(count, 5);

let mut w = ref_cell.borrow_mut(); // panic: already borrowed
w.push_str(" world");

// 例 2:
pub struct SpiderRobot {
    // ...
    log_file: RefCell<File>,
    // ...
}

impl SpiderRobot {
    pub fn log(&self, message: &str) {
        // self 虽然是不可变引用, 但是使用 RefCell<T>.borrow_mut() 来返 RefMut<T>
        let mut file = self.log_file.borrow_mut();
        writeln!(file, "{}", message).unwrap();
    }
}

Cell/RefCell 没有实现 Sync trait, Rust 不允许多线程中使用它们

Mutext<T>/RwLock<T>/OnceLock<T>/原子操作类型,提供了线程安全的内部可变性,都可以使用共享引用 &self 来调用它们的 &mut 方法,例如 lock()。

OnceCell 只能初始化一次,具有内部可变性的智能指针:

  • 一般使用 get_or_init() 方法来进行初始化;
  • 不能在多线程环境中使用,但可以使用 std::sync::OnceLock;
use std::cell::OnceCell;

let cell = OnceCell::new();
assert!(cell.get().is_none()); // get() 返回 Option,如果未设置则返回 None

let value: &String = cell.get_or_init(|| {
    "Hello, World!".to_string()
});
assert_eq!(value, "Hello, World!");
assert!(cell.get().is_some());

// set(): 如果已设置,则返回 Err
let cell = OnceCell::new();
assert!(cell.get().is_none());
assert_eq!(cell.set(92), Ok(()));
assert_eq!(cell.set(62), Err(62));
assert!(cell.get().is_some());

LazyCell 和 OnceCell 类似,但是在 new() 函数中传入初始化逻辑,通过实现 Deref<Target=T> 来 第一次解引用时自动调用

  • LayCell 不支持在多线程环境中使用,可以使用 std::sync::LazyLock;
use std::cell::LazyCell;

let lazy: LazyCell<i32> = LazyCell::new(|| {
    println!("initializing");
    92
});
println!("ready");
println!("{}", *lazy);
println!("{}", *lazy);

Cell 通常和 Rc/Arc 结合使用:Rc/Arc<T> 是引用计数共享,所以 T 只能是 &T 共享引用类型 ,而通过和 Cell 结合使用,可以实现可修改的单例模式:

static GLOBL :Arc<Mutex<i32>> = Arc::new(Mutex::New(1));

6 std::collector
#

数组 [N; T] 和各种容器类型都没有实现 Display trait, 但是实现了 Debug trait。

Option/Result 都是 enum 类型,但是也支持迭代(实现了 IntoIterator),效果就如一个或 0 个元素。

6.1 Vec
#

Vec 是相同类型元素,动态大小, 在 heap 上分配的连续内存块. 可以被 index 操作 &v[a..b] 生成指向对应内存区域 slice. 有三个字段: 长度, 容量和指向堆内存首地址的指针.

可以使用 push 向 Vec 添加元素, Rust 自动扩充 Vec 的内存块大小, 这种扩容将老的 Vec 内容 move 到新的连续内存块, 所以:

  1. 有性能开销, 涉及到内存数据的复制移动;
  2. 会导致已有的 Vec Item 的引用失效, 所以在有共享引用的情况下不能修改 Vec;

为了避免在 push 过程中容量增长带来的开销, 可以使用 Vec::with_capacity(n) 来一次性创建容量为 n 的 Vec.

Vec 只能高效的在尾部进行 push/pop 操作, 如果在中间 insert/remove 元素, 则涉及后续元素的移动, 所以 Vec 越长, 中间插入和删除元素性能越差.

Vec 创建和操作:

  • vec[a..b] 的 a 和 b 类似是 usize, 必须小于 vec.len() 否则会 panic;
  • vec.get(index) 返回一个 Option, 当 index 不存在时返回 None;
// 创建一个空的 vector, len/capacity 均为 0
let mut numbers: Vec<i32> = vec![];

// 用给定的内容创建一个vector, len/capacity 等于元素数目
let words = vec!["step", "on", "no", "pets"];
let mut buffer = vec![0u8; 1024]; // 1024 个 0字节

// 将一个其他集合转换成vector
let my_vec = my_set.into_iter().collect::<Vec<String>>();

// 获取一个元素的引用
let first_line = &lines[0];
// 获取一个元素的拷贝
let fifth_number = numbers[4];
let second_number = lines[1].clone();
// 获取一个切片的引用
let my_ref = &buffer[4..12];

// 获取一个切片的拷贝:slice.to_vec(), Clone 切片生成 Vec
let my_copy = buffer[4..12].to_vec(); // Vec<T>
let v = [1, 2, 3, 4, 5, 6, 7, 8, 9];
assert_eq!(v.to_vec(), vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(v[0..6].to_vec(), vec![1, 2, 3, 4, 5, 6]);

// 将iterable的所有item按顺序添加到vec的末尾。它类似于多值版本的.push()。 iterable 参数可以是任何
// 实现了 IntoIterator<Item=T>。
vec.extend(iterable);

// 类似于 vec.truncate(index),除了它返回一个 Vec<T> 包含 vec 尾部被移除的元素。它类似于.pop() 的
// 多值版本。
vec.split_off(index); // 返回一个 Vec 包含 index 及以后的元素, 原来的 vec 只包含 index 前的元素

// 将 vec2 的内容添加到 vec, 然后 vec2 被清空. 这类似于 vec.extend(vec2),除了调用之后 vec2 仍然存
// 在,并且容量不变。
vec.append(&mut vec2);

// 这会从vec中移除范围vec[range],并返回一个被移除元素的迭代器,其中 range 是一个范围值,例如
// .. 或0..4。
vec.drain(range);

// 移除所有没有通过给定测试的方法。类似于 vec = vec.into_iter().filter(test).collect();
vec.retain(test);

不能在借用 Vec 元素的情况下,修改 Vec 本身(这是由于修改 Vec 时可能会重新分配内存,从而导致借用的指针失效):tuple/struct 是支持部分 field 修改的。

// 不能同时 &mut 借用 Vec 的元素
let mut v = vec![0, 1, 2, 3];
let a = &mut v[i];
let b = &mut v[j]; // error: 不能同时借用`v`的多个可变引用。

let first = &v[0];
v.push(6) // erorr:first 共享借用 Vec,不能再修改(需要可变借用) Vec
println!("{}", first);

如果类型 T 支持 = 和! 运算符(PartialEq trait,见相等性比较),那么数组 [T; N]、切 片 [T]、vector Vec<T> 也支持这些运算符。当两个切片的长度和相应的元素都相等时两个切 片才相等。数组和 vector 也是一样。

如果 T 支持运算符 <、<=、>、>=(PartialOrd trait,见顺序性比较),那么 T 的数组、切片 和 vector 也支持。切片的比较按照字典序进行。

创建 Vec 的宏 vec![] 和 vec!() 是等效的。

Vec 方法:由于 Vec<T> 可以被 Deref<Targe=[T]>, 所以 Vec 对象也继承了 slice [T] 的方法:

// 创建
pub const fn new() -> Vec<T>
let mut vec: Vec<i32> = Vec::new();

pub fn with_capacity(capacity: usize) -> Vec<T>
let mut vec = Vec::with_capacity(10);
// The vector contains no items, even though it has capacity for more
assert_eq!(vec.len(), 0);
assert!(vec.capacity() >= 10);

pub unsafe fn from_raw_parts( ptr: *mut T, length: usize, capacity: usize ) -> Vec<T>

impl<T, A> Vec<T, A> where A: Allocator,
pub const fn new_in(alloc: A) -> Vec<T, A>
pub fn with_capacity_in(capacity: usize, alloc: A) -> Vec<T, A>
pub unsafe fn from_raw_parts_in( ptr: *mut T, length: usize, capacity: usize, alloc: A ) -> Vec<T, A>
pub fn into_raw_parts(self) -> (*mut T, usize, usize)
pub fn into_raw_parts_with_alloc(self) -> (*mut T, usize, usize, A)

pub fn capacity(&self) -> usize
pub fn reserve(&mut self, additional: usize) // 确保 capacity() >= len() + additional
let mut vec = vec![1];
vec.reserve(10);
assert!(vec.capacity() >= 11);

pub fn reserve_exact(&mut self, additional: usize)
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>
pub fn try_reserve_exact( &mut self, additional: usize ) -> Result<(), TryReserveError>

pub fn shrink_to_fit(&mut self)
let mut vec = Vec::with_capacity(10);
vec.extend([1, 2, 3]);
assert!(vec.capacity() >= 10);
vec.shrink_to_fit();
assert!(vec.capacity() >= 3);

// 将 vector 的 capacity 收缩到指定的最小值
pub fn shrink_to(&mut self, min_capacity: usize)
let mut vec = Vec::with_capacity(10);
vec.extend([1, 2, 3]);
assert!(vec.capacity() >= 10);
vec.shrink_to(4);
assert!(vec.capacity() >= 4);
vec.shrink_to(0);
assert!(vec.capacity() >= 3);

// 将 vec 转换为 Box<[T]> , 该方法会丢弃 capacity 超过 shink_to_fit 的空间
pub fn into_boxed_slice(self) -> Box<[T], A>
let mut vec = Vec::with_capacity(10);
vec.extend([1, 2, 3]);
assert!(vec.capacity() >= 10);
let slice = vec.into_boxed_slice();
assert_eq!(slice.into_vec().capacity(), 3);

pub fn truncate(&mut self, len: usize)
let mut vec = vec![1, 2, 3];
vec.truncate(8);
assert_eq!(vec, [1, 2, 3]);

// 等效于 &s[..]
pub fn as_slice(&self) -> &[T]
use std::io::{self, Write};
let buffer = vec![1, 2, 3, 5, 8];
io::sink().write(buffer.as_slice()).unwrap();

pub fn as_mut_slice(&mut self) -> &mut [T]
pub fn as_ptr(&self) -> *const T
pub fn as_mut_ptr(&mut self) -> *mut T

// 返回底层使用的内存分配器
pub fn allocator(&self) -> &A

// 将长度设置为 new_len,一般使用安全方法 truncate, resize, extend, or clear.
// new_len 需要小于等于 capacity(), 而且 old_len..new_len must be initialized.
pub unsafe fn set_len(&mut self, new_len: usize)

// 使用 vec 最后一个元素替换 index 元素,返回 index 元素
pub fn swap_remove(&mut self, index: usize) -> T
let mut v = vec!["foo", "bar", "baz", "qux"];
assert_eq!(v.swap_remove(1), "bar");
assert_eq!(v, ["foo", "qux", "baz"]);
assert_eq!(v.swap_remove(0), "foo");
assert_eq!(v, ["baz", "qux"]);

// 插入和删除元素,会导致后续元素批量移动。
pub fn insert(&mut self, index: usize, element: T)
pub fn remove(&mut self, index: usize) -> T

// 只保留 f 返回 true 的元素
pub fn retain<F>(&mut self, f: F) where F: FnMut(&T) -> bool
let mut vec = vec![1, 2, 3, 4];
vec.retain(|&x| x % 2 == 0);
assert_eq!(vec, [2, 4]);

pub fn retain_mut<F>(&mut self, f: F) where F: FnMut(&mut T) -> bool

// 使用指定 F 来过滤重复的元素
pub fn dedup_by_key<F, K>(&mut self, key: F) where F: FnMut(&mut T) -> K, K: PartialEq
let mut vec = vec![10, 20, 21, 30, 20];
vec.dedup_by_key(|i| *i / 10);
assert_eq!(vec, [10, 20, 30, 20]);

pub fn dedup_by<F>(&mut self, same_bucket: F) where F: FnMut(&mut T, &mut T) -> bool
let mut vec = vec!["foo", "bar", "Bar", "baz", "bar"];
vec.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
assert_eq!(vec, ["foo", "bar", "baz", "bar"]);

pub fn push(&mut self, value: T)
pub fn push_within_capacity(&mut self, value: T) -> Result<(), T>
pub fn pop(&mut self) -> Option<T>

// 将 other 的元素移动到 self,other 被清空(还是可以访问的):
pub fn append(&mut self, other: &mut Vec<T, A>)
let mut vec = vec![1, 2, 3];
let mut vec2 = vec![4, 5, 6];
vec.append(&mut vec2);
assert_eq!(vec, [1, 2, 3, 4, 5, 6]);
assert_eq!(vec2, []);

// 从 self 删除 range 的元素,返回 range 元素的迭代器
pub fn drain<R>(&mut self, range: R) -> Drain<'_, T, A> where R: RangeBounds<usize>
let mut v = vec![1, 2, 3];
let u: Vec<_> = v.drain(1..).collect();
assert_eq!(v, &[1]);
assert_eq!(u, &[2, 3]);
// A full range clears the vector, like `clear()` does
v.drain(..);
assert_eq!(v, &[]);

pub fn clear(&mut self)
pub fn len(&self) -> usize
pub fn is_empty(&self) -> bool

// 将 self 从 at 位置切分为两个,self 为 at 前元素,返回 at 后元素
pub fn split_off(&mut self, at: usize) -> Vec<T, A> where A: Clone
let mut vec = vec![1, 2, 3];
let vec2 = vec.split_off(1);
assert_eq!(vec, [1]);
assert_eq!(vec2, [2, 3]);

// 调整 vec,使其长度打到 new_len, 新的元素使用 f 返回
pub fn resize_with<F>(&mut self, new_len: usize, f: F) where F: FnMut() -> T
let mut vec = vec![1, 2, 3];
vec.resize_with(5, Default::default);
assert_eq!(vec, [1, 2, 3, 0, 0]);
let mut vec = vec![];
let mut p = 1;
vec.resize_with(4, || { p *= 2; p });
assert_eq!(vec, [2, 4, 8, 16]);

// 返回一个具有指定 lifetime 的 slice
pub fn leak<'a>(self) -> &'a mut [T] where A: 'a

pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<T>]
pub fn split_at_spare_mut(&mut self) -> (&mut [T], &mut [MaybeUninit<T>])
impl<T, A> Vec<T, A> where T: Clone, A: Allocator, pub fn resize(&mut self, new_len: usize, value: T)
let mut vec = vec!["hello"];
vec.resize(3, "world");
assert_eq!(vec, ["hello", "world", "world"]);
let mut vec = vec![1, 2, 3, 4];
vec.resize(2, 0);
assert_eq!(vec, [1, 2]);

pub fn extend_from_slice(&mut self, other: &[T])
let mut vec = vec![1];
vec.extend_from_slice(&[2, 3, 4]);
assert_eq!(vec, [1, 2, 3, 4]);

pub fn extend_from_within<R>(&mut self, src: R) where R: RangeBounds<usize>

impl<T, A> Vec<T, A> where A: Allocator

// 将 range 元素用 replace_with 替换, 返回替换前的 range 元素
pub fn splice<R, I>( &mut self, range: R, replace_with: I) -> Splice<'_, <I as IntoIterator>::IntoIter, A> where R: RangeBounds<usize>, I: IntoIterator<Item = T>
let mut v = vec![1, 2, 3, 4];
let new = [7, 8, 9];
let u: Vec<_> = v.splice(1..3, new).collect();
assert_eq!(v, &[1, 7, 8, 9, 4]);
assert_eq!(u, &[2, 3]);

pub fn extract_if<F>(&mut self, filter: F) -> ExtractIf<'_, T, F, A> where F: FnMut(&mut T) -> bool
#![feature(extract_if)]
let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15];
let evens = numbers.extract_if(|x| *x % 2 == 0).collect::<Vec<_>>();
let odds = numbers;
assert_eq!(evens, vec![2, 4, 6, 8, 14]);
assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]);

6.2 VecDeque
#

VecDeque 是动态大小, 在 heap 上分配内存块的环形缓冲区, 有 start 和 end 指针。

和 Vec 不同的是, 数据并不是从内存区域的开始存储, 也可以在尾部回环(自动管理), 所以内存块不一定是连续的,故它没有实现 Deref<Target=[T]>, 即不能调用 slice 的方法。

VecDeque 支持 index 操作, 如 deque[index], 但是在内存不一定是连续存储元素, 所以不能创建 slice 和继承 slice 的方法,主要是能快速的开头和尾部 push/pop 元素.

6.3 LinkedList
#

LinkedList 是双端链接的 List,它允许从两端 push 和 pop 元素。

use std::collections::LinkedList;
let list = LinkedList::from([1, 2, 3]);

提供的部分方法:

pub const fn new() -> LinkedList<T>
pub fn append(&mut self, other: &mut LinkedList<T>)

pub fn front(&self) -> Option<&T>
pub fn front_mut(&mut self) -> Option<&mut T>
pub fn back(&self) -> Option<&T>
pub fn back_mut(&mut self) -> Option<&mut T>
pub fn push_front(&mut self, elt: T)
pub fn pop_front(&mut self) -> Option<T>
pub fn push_back(&mut self, elt: T)
pub fn pop_back(&mut self) -> Option<T>

6.4 BinaryHeap
#

BinaryHeap<T> 集合始终以某种形式组织元素,其中最大的元素总是会被移动到队列的首部。BinaryHeap 并不仅限于数字,它可以包含任何实现了内建的 Ord trait 的类型。

heap.push(value); // 向堆中添加一个元素
heap.pop(); // 移除并返回堆中最大的值。返回 Option<T>,如果堆为空时返回 None。
heap.peek(); //返回堆中最大的值的引用。返回类型是 Option<&T>。
if let Some(top) = heap.peek_mut() {
    if *top > 10 {
        PeekMut::pop(top);
    }
}

use std::collections::BinaryHeap;
let mut heap = BinaryHeap::from(vec![2, 3, 8, 6, 9, 5, 4]);
// 值 9 在堆的顶部:
assert_eq!(heap.peek(), Some(&9));
assert_eq!(heap.pop(), Some(9));
// 移除 9 也会重新排布其他元素,把 8 移动到头部,等等:
assert_eq!(heap.pop(), Some(8));
assert_eq!(heap.pop(), Some(6));
assert_eq!(heap.pop(), Some(5));

BinaryHeap 可以用作一个工作队列。定义一个任务结构体,然后根据任务的优先级实现 Ord,让高优先级的任务大于低优先级的任务。然后创建一个 BinaryHeap 来保存所有待办的任务。它的 .pop() 方法将总是返回最重要的任务。

BinaryHeap 是可迭代的对象,并且它有.iter() 方法,但这个迭代器 以任意顺序 产生堆中的元素,而不是按照从大到小的顺序。为了按照大小顺序消耗 BinaryHeap 中的值,可以使用 while pop 循环:

while let Some(task) = heap.pop() {
    handle(task);
}

6.5 HashMap
#

HashMap 是键值对(称为条目 entry)的集合,条目的键都不同,所有的条目按照一定结构组织,方便高效查找。

Rust 提供两种 map 类型: HashMap<K, V> 和 BTreeMap<K, V>。它们共享了很多相同的方法, 不同之处在于它们组织条目的方式。

  1. HashMap: 把键和值都存储在哈希表中,因此它要求键的类型 K 实现了 Hash 和 Eq ,这两个 trait 分别用于哈希和相等性比较。

    • bool、int、uint、String、&str 等;
    • float 没有实现这两个 trait,不能用于 key;
    • 对于 collection,如果它们的元素类型都实现了 Hash 和 Eq,则 collection 也实现了 Hash 和 Eq,例如 Vec<u8>;
  2. BTreeMap: 按照键的顺序在 树形结构中 存储条目,因此它要求键的类型 K 实现了 Ord。同样,深色区域表示没有被使用的空间。

可见:

  1. HashMap 是使用 一块连续的 heap 来保存 map 元素的, 如果 map 容量要增加, 则需要分配一块新的连续内存区域, 并将老的 map 元素移动过去,所以有一定性能开销;
  2. BTreeMap 是使用 Node 来保存元素的, 所以不是连续内存区域, 便于随机插入和读取 ;
// 从键值对创建并填充新的 HashMap 或 BTreeMap。iter 必须是一个 Iterator<Item=(K, V)>。
iter.collect();

// 如果 map 里有给定 key 的条目则返回 true。
map.contains_key(&key);

// 在 map 中查找给定 key 的条目。如果找到了匹配的条目,就返回 Some(r),其中 r 是相 应的值的引用。
// 否则返回 None。
map.get(&key);

在查询 map 时,传入的 key 类型 B 和 map 定义的 key 类型 K 可以不一致,需要满足 B = Borrow<K>;

map 实现了 Index trait,故支持 map[&key] 操作,然而如果没有给定的 key的条目存在, 则会 panic ,就类似越界访问数组一样。

不支持 map[&key] = value 赋值 ,但可以使用 map.insert(key, value) 来插入和返回值(旧值)。

因为 BTreeMap<K, V> 按照键的顺序保存条目,所以它支持一个附加的操作:

btree_map.split_off(&key)

把 btree_map 分割成两个。键小于 key 的条目被留在 btree_map 中,返回一个包含其余条目的新BTreeMap<K, V>。

map 支持 Entry 操作:

let record = student_map.entry(name.to_string()).or_insert_with(Student::new);

student_map.entry(name.to_string()) 返回的 Entry 值就像一个 可变引用 ,它指向 map 中一个已经被键值对占据 (occupied) 的位置或者是空的 (vacant),意思是还没有条目占据这个位置。如果为空,条目的 .or_insert_with() 方法会插入一个新的 Student。

  • Entry 以 &mut 获得 map 的可变引用;
  • entry(key) 的参数 key 类型必须和 map 的 key 类型 一致 。(不支持 k = Borrowed<Q>)
// 对给定的 key 返回一个 Entry。如果 map 中没有这个 key,它会返回一个空的 Entry。 这个方法以 &mut
// 引用获取 self 参数,并返回一个生命周期相同的 Entry:
pub fn entry<'a>(&'a mut self, key: K) -> Entry<'a, K, V>
// Entry 类型有一个生命周期参数'a,因为它是 map 的一种 mut 借用。只要 Entry 存在,它就有 map 的独
// 占访问权限。
//
// 不幸的是,如果 map 的键的类型为 String,那么不能向这个方法传递 &str 类型的参 数。这种情况下
// 的.entry() 方法需要一个真实的 String。因为 entry 的输入参数 key 是 K 类型, 而非 Borrow<Q> 类型;
map.entry(key);

// 确保 map 包含给定的 key 的条目,如果需要的话用给定的 value 插入一个新的条目。它 返回新插入的或者
// 现有的值的 mut 引用。
map.entry(key).or_insert(value);

let mut vote_counts: HashMap<String, usize> = HashMap::new();
for name in ballots {
    let count = vote_counts.entry(name).or_insert(0); // .or_insert()返回一个可变引用,因此count的类型是&mut usize。
    *count += 1;
}

// 确保 map 包含给定的 key 的条目,如果需要的话用 Default::default() 返回的值插入 一个新条目
map.entry(key).or_default();

// 这个方法也一样,除了当它需要创建新的条目时,它会调用 default_fn() 来产生默认 值。
map.entry(key).or_insert_with(default_fn);

// 这个map中包含每个单词和出现它的文件的集合。
let mut word_occurrence: HashMap<String, HashSet<String>> = HashMap::new(); for file in files {
    for word in read_words(file)? {
        let set = word_occurrence
            .entry(word)
            .or_insert_with(HashSet::new);
        set.insert(file.clone());
    } }


// 如果给定的 key 的条目存在就调用 closure,把值的可变引用传进闭包。它返回一 个 Entry,因此它可以
// 和其它方法链式调用。
map.entry(key).and_modify(closure);

// 这个map包含给定字符串中的所有单词,以及它们出现的次数。
let mut word_frequency: HashMap<&str, u32> = HashMap::new(); for c in text.split_whitespace() {
    word_frequency.entry(c)
        .and_modify(|count| *count += 1)
        .or_insert(1);
}

迭代 map 的方法:

  1. 以值迭代:for (k, v) in map,产生(K, V)对。这会消耗掉 map。
  2. 迭代共享引用:for (k, v) in &map,产生(&K, &V)对。
  3. 迭代可变引用:for (k, v) in &mut map,产生(&K, &mut V)对。

没有获取 map 中 key 的 mut 访问的方法,因为条目是按照键来组织的。

另外:

  1. map.keys() 返回一个只迭代键的迭代器,以引用的形式返回。
  2. map.values() 返回一个只迭代值的迭代器,以引用的形式返回。
  3. map.values_mut() 返回一个只迭代值的迭代器,以 mut 引用的形式返回。

所有的 HashMap 迭代器都会以 任意顺序 访问 map 的条目。BTreeMap 的迭代器会按照 键的顺序 访问它们。

6.6 HashSet/BTreeSet
#

map 和 set 有不同的方法,但其实一个 set 就是一个只有键而不是键值对的 map。事实上 Rust 的HashSet<T> 和 BTreeSet<T> 被实现为 HashMap<T, ()> 和 BTreeMap<T, ()> 的浅包装。

HashMap 和 HashSet 的 key 必须要实现 Hash 和 Eq。

两种迭代 set 的方法:

  1. 以值迭代(for v in set)产生set的成员(并消耗这个 set)。
  2. 以共享引用迭代(for v in &set)产生set中成员的共享引用。

不支持以 mut 引用迭代 set。也没有方法获取 set 中值的 mut 引用。

set.iter() 返回一个以共享引用方式迭代 set 的迭代器。

HashSet 迭代器类似于 HashMap 的迭代器,也会以任意顺序产生值。BTreeSet 迭代器按照顺序产生值,类似于一个排序过的 Vec。

set 的一些方法:

  1. set.get(&value): 返回 value 的共享引用,类型是 Option<&T>;
  2. set.take(&value): 与 set.remove(&value) 类似,但是会返回移除的值,类型是 Option<&T>;
  3. set.replace(value);

针对 set 间的方法:

  1. set1.intersection(&set2) : 返回同时出现在 set1 和 set2 中的值的迭代器;
  2. &set1 & &set2: 返回一个新 set,是 set1 和 set2 的交集;
  3. set1.union(&set2): 返回并集迭代器;等效于&set1 | &set2
  4. set1.difference(&set2): 返回差集迭代器; 等效于 &set1 - &set2
  5. set1.symmetric_difference(&set2): 返回对称差(异或)迭代器,等效于: &set1 ^ &set2;

6.7 Hash
#

std::hash::Hash 是标准库用于可哈希类型的 trait。HashMap 的键和 HashSet 的元素必须实现 Hash 和 Eq。

大多数实现了 Eq 的内建类型也都实现了 Hash。整数类型、char、String 都是可哈希类型; 当元素是可哈希类型时,元组、数组、切片、vector 也是可哈希类型。

标准库的一个原则是不管你把一个值存储到哪里或者如何指向它,它必须有相同的哈希值。因此,引用和被引用的值有相同的哈希值。Box 和被装箱的值有相同的哈希值。一 个 vector vec 和包含它的所有元素的切片 &vec[..] 有相同的哈希值。一个 String 和一个有相 同字符的 &str 有相同的哈希值。

结构和枚举默认没有实现 Hash,但可以派生实现:

/// 大英博物馆藏品中的对象的ID
#[derive(Clone, PartialEq, Eq, Hash)]
enum MuseumNumber {
    // ...
}

只要所有的字段都是可哈希的就可以正常工作。

7 std::panic
#

panic 是最简单的异常处理机制,它先打印 error message,然后开始 unwinding stack,最后退出当前 thread:

  1. 如果是 main thread panic,则程序退出;
  2. 否则,如果是 spawned thread panic,则该 thread 会终止,程序不退出;

unwinding stack 过程中,Rust 会回溯调用栈,drop 调用 stack 涉及的所有对象和资源。

使用 RUST_BACKTRACE=1 cargo run 来打印 stack 详情:

fn drink(beverage: &str) {
    // You shouldn't drink too much sugary beverages.
    if beverage == "lemonade" { panic!("AAAaaaaa!!!!"); }

    println!("Some refreshing {} is all I need.", beverage);
}

fn main() {
    drink("water");
    drink("lemonade");
    drink("still water");
}

注意:如果 panic 是 FFI 调用的外部库函数导致的,则 Rust 不会进行 unwinding,而是直接 panic。

也可以在 Cargo.toml 里设置 panic 时不 unwiding stack 而是直接 abort 退出(如果设置了 panic hook,则 abort 退出前会先调用该 hook):

[profile.release]
panic = 'abort'

std::panic module 提供的函数:

  • catch_unwind : Invokes a closure, capturing the cause of an unwinding panic if one occurs.
  • panic_any : Panic the current thread with the given message as the panic payload.
  • resume_unwind : Triggers a panic without invoking the panic hook.
  • set_hook : Registers a custom panic hook, replacing the previously registered hook.
  • take_hook : Unregisters the current panic hook and returns it, registering the default hook in its place.
  • always_abort : Make all future panics abort directly without running the panic hook or unwinding.
  • get_backtrace_style : Checks whether the standard library’s panic hook will capture and print a backtrace.
  • set_backtrace_style : Configure whether the default panic hook will capture and display a backtrace.
  • update_hook : Atomic combination of take_hook and set_hook. Use this to replace the panic handler with a new panic handler that does something and then executes the old handler.

std::panic::cach_unwind() 可以 捕获闭包中的 panic ,该函数返回一个 Result:

  • 当闭包内 panic 时,返回 Err 否则返回 Ok;
  • 如果 panic 使用 unwinding 实现,则会捕获。否则如果是 abort 实现,则不会捕获;
  • 如果设置了 panic hook, 则该 hook 在 panic 被捕获前被 unwinding 前调用;
  • Also note that unwinding into Rust code with a foreign exception (e.g. an exception thrown from C++ code) is undefined behavior.
pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R>

use std::panic;

let result = panic::catch_unwind(|| {
    println!("hello!");
});
assert!(result.is_ok());

let result = panic::catch_unwind(|| {
    panic!("oh no!");
});
assert!(result.is_err());

std::panic::resume_unwind() 来触发一个 不执行 panic hook 的 panic:

  • 如果 panic 被配置使用 abort 实现,则该函数会 abort 程序而不是 unwinding;
pub fn resume_unwind(payload: Box<dyn Any + Send>) -> !

use std::panic;

let result = panic::catch_unwind(|| {
    panic!("oh no!");
});

if let Err(err) = result {
    panic::resume_unwind(err);
}

std::panic::panic_any(msg: M) 来触发 执行 panic hook 的 panic ,其中 M 可以是任意类型:

  • panic!() 的参数只能是和 println!() 类似的格式化字符串。
// msg 可以任意类型(Any + Send)而不只是 string。
// 后续可以使用 PanicInfo::payload() 来获取 mesage。
pub fn panic_any<M: 'static + Any + Send>(msg: M) -> !

panic!();
panic!("this is a terrible mistake!");
panic!("this is a {} {message}", "fancy", message = "message");
std::panic::panic_any(4); // panic with the value of 4 to be collected elsewhere

std::panic::set_hook() 设置自定义 panic hook(只能生效一个,取代以前的 hook)

  • 在 thread panic 时,但 panic runtime 调用前执行,所以对于 aborting 和 unwinding 都适用
  • 缺省的 hook 是程序启动时自动注册的,会在 stderr 打印 message,并按需打印 backtrace;
pub fn set_hook(hook: Box<dyn Fn(&PanicInfo<'_>) + Sync + Send + 'static>)

// 示例
use std::panic;

panic::set_hook(Box::new(|_| {  // panic 时传入一个 PanicInfo 对象,可以用来提取 panic 信息
    println!("Custom panic hook");
}));

panic!("Normal panic");

std::panic::take_hook() 将 panic hook 设置为缺省 hook:

pub fn take_hook() -> Box<dyn Fn(&PanicInfo<'_>) + Sync + Send + 'static>

use std::panic;

panic::set_hook(Box::new(|_| {
    println!("Custom panic hook");
}));

let _ = panic::take_hook();

panic!("Normal panic");

std::panic::update_hook() 是 take_hook/set_hook 的原子操作,用来替换当前 hook 并返回以前的 hook:

#![feature(panic_update_hook)]
use std::panic;

// Equivalent to
// let prev = panic::take_hook();
// panic::set_hook(move |info| {
//     println!("...");
//     prev(info);
// );
panic::update_hook(move |prev, info| {
    println!("Print custom message and execute panic handler as usual");
    prev(info);
});

panic!("Custom and then normal");

set_hook/get_hook 使用的 std::panic::PanicInfo 对象提供了 location()/message()/payload() 方法,提供 panic 信息:

use std::panic;

panic::set_hook(Box::new(|panic_info| {
    if let Some(location) = panic_info.location() {
        println!("panic occurred in file '{}' at line {}",
            location.file(),
            location.line(),
        );
    } else {
        println!("panic occurred but can't get location information...");
    }
}));

panic!("Normal panic");

std::panic::set_backtrace_style/ std::panic::get_backtrace_style() 用于设置和返回 panic 时 panic hook 是否能捕获和显示 backtrace:

  • 缺省值可以使用环境变量 RUST_BACKTRACE 来配置。
  • for BacktraceStyle::Off
  • for BacktraceStyle::Full
  • for BacktraceStyle::Short

Other values are currently BacktraceStyle::Short, but this may change in the future

#[non_exhaustive]
pub enum BacktraceStyle {
    Short,
    Full,
    Off,
}

get_backtrace_style() 先查找 set_backtrace_style() 设置的值,如果没有设置则查找 RUST_BACKTRACE 环境变量。

pub fn set_backtrace_style(style: BacktraceStyle)
pub fn get_backtrace_style() -> Option<BacktraceStyle>

8 std::error
#

Rust 提供了两种错误处理类型:

  1. panic runtime 和接口;
  2. Result, std::error::Error trait 和用户自定义错误类型;

通过使用如下两个方法,可以将 Error 转换为 panic:

  • Result::unwrap()
  • Result::expect(&str)

Trait std::error::Error:

  • Error trait 没有必须方法;
  • Error trait 是 Debug + Display 的子 trait,所以实现 Error trait 的类型必须同时实现 Display 和 Debug trait。
  • 报错信息一般是小写字符串;
pub trait Error: Debug + Display {
    // 返回引发该 Error 的深层次错误,该错误类型必须也实现了 Error trait。
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }

    // description 和 cause 方法已被弃用
    fn description(&self) -> &str { ... } // 用 to_string() 代替
    fn cause(&self) -> Option<&dyn Error> { ... } // 用 source() 代替

    // 很少需要定义,目前仅标准库内部使用(std::error::Report 的 show_backtrace() 方法需要)
    fn provide<'a>(&'a self, request: &mut Request<'a>) { ... }
}

let err = "NaN".parse::<u32>().unwrap_err();
assert_eq!(err.to_string(), "invalid digit found in string");

自定义实现 Error trait 的类型:

  1. 必须实现 Display 和 Debug trait;
  2. 可以重新定义 Error::source() 方法,后续调用方获得下一层(根因)错误:
use std::error::Error;
use std::fmt;

// 自定义错误类型必须同时实现 Debug 和 Display trait
#[derive(Debug)]
struct SuperError {
    source: SuperErrorSideKick,
}

impl fmt::Display for SuperError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SuperError is here!")
    }
}

impl Error for SuperError {
    // SuperError 重新定义了 source 方法,返回内部(更底层)的错误的借用
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.source)
    }
}

#[derive(Debug)]
struct SuperErrorSideKick;

impl fmt::Display for SuperErrorSideKick {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SuperErrorSideKick is here!")
    }
}

// Error 都是可选方法或函数,所以可以不实现。
impl Error for SuperErrorSideKick {}

fn get_super_error() -> Result<(), SuperError> {
    Err(SuperError { source: SuperErrorSideKick })
}

fn main() {
    match get_super_error() {
        Err(e) => {
            println!("Error: {e}");
            println!("Caused by: {}", e.source().unwrap()); // 手动调用 source() 方法来返回内部根因
        }
        _ => println!("No error"),
    }
}

标准库为实现 Error 的 trait object,提供了 is/downcase_xx<T>)() 方法,用于对 Error 类型进行 下钻处理

// 下面 3 种类型均提供了如下 4 个方法。
impl dyn Error
impl dyn Error + Send
impl dyn Error + Send + Sync

// 以 impl dyn Error 为例
pub fn is<T>(&self) -> bool where T: Error + 'static
pub fn downcast_ref<T>(&self) -> Option<&T> where T: Error + 'static
pub fn downcast_mut<T>(&mut self) -> Option<&mut T> where T: Error + 'static
pub fn downcast<T>( self: Box<dyn Error> ) -> Result<Box<T>, Box<dyn Error>> where T: Error + 'static

也可以使用 std::error module 提供的两个函数来返回 trait object 的类型引用或值:

request_ref
Request a reference of type T from the given impl Error.
request_value
Request a value of type T from the given impl Error.
// 从 trait object 返回引用
pub fn request_ref<T, 'a>(err: &'a (impl Error + ?Sized)) -> Option<&'a T> where T: 'static + ?Sized,
use core::error::Error;
use core::error::request_ref;

fn get_str(err: &impl Error) -> &str {
    request_ref::<str>(err).unwrap()
}

// 从 trait object 返回 value
pub fn request_value<T, 'a>(err: &'a (impl Error + ?Sized)) -> Option<T> where T: 'static,
use std::error::Error;
use core::error::request_value;
fn get_string(err: &impl Error) -> String {
    request_value::<String>(err).unwrap()
}

Struct std::error::Report<<E=Box<dyn Error>> 用来打印 E 的信息:

pub fn new(error: E) -> Report<E>
pub fn pretty(self, pretty: bool) -> Self
pub fn show_backtrace(self, show_backtrace: bool) -> Self

// 示例
#![feature(error_reporter)]
use std::error::Report;

fn main() -> Result<(), Report<SuperError>> {
    get_super_error()
        .map_err(Report::from)
        .map_err(|r| r.pretty(true).show_backtrace(true))?;
    Ok(())
}

// 输出
// Error: SuperError is here!

// Caused by:
//       SuperErrorSideKick is here!


#![feature(error_reporter)]
use std::error::Report;

let source = SuperErrorSideKickSideKick;
let source = SuperErrorSideKick { source };
let error = SuperError { source };
let report = Report::new(error).pretty(true);
eprintln!("Error: {report:?}");

// 输出
// Error: SuperError is here!

// Caused by:
//    0: SuperErrorSideKick is here!
//    1: SuperErrorSideKickSideKick is here!

std::error::Report 的 show_backtrace() 方法使用 Error 实现的 provide() 方法来打印 backtrace:

#![feature(error_reporter)]
#![feature(error_generic_member_access)]
use std::error::Request;
use std::error::Report;
use std::backtrace::Backtrace;

#[derive(Debug)]
struct SuperErrorSideKick {
    backtrace: Backtrace,
}

impl SuperErrorSideKick {
    fn new() -> SuperErrorSideKick {
        SuperErrorSideKick { backtrace: Backtrace::force_capture() } // 捕获 backtrace
    }
}

impl Error for SuperErrorSideKick {
    fn provide<'a>(&'a self, request: &mut Request<'a>) {
        request.provide_ref::<Backtrace>(&self.backtrace); // 提供 Backtrace 类型
    }
}

let source = SuperErrorSideKick::new();
let error = SuperError { source };
// show_backtrace 依赖于 Error 实现 provide() 方法。
let report = Report::new(error).pretty(true).show_backtrace(true);
eprintln!("Error: {report:?}");

// 输出:
// Error: SuperError is here!

// Caused by:
//       SuperErrorSideKick is here!

// Stack backtrace:
//    0: rust_out::main::_doctest_main_src_error_rs_1158_0::SuperErrorSideKick::new
//    1: rust_out::main::_doctest_main_src_error_rs_1158_0
//    2: rust_out::main
//    3: core::ops::function::FnOnce::call_once
//    4: std::sys_common::backtrace::__rust_begin_short_backtrace
//    5: std::rt::lang_start::{{closure}}
//    6: std::panicking::try
//    7: std::rt::lang_start_internal
//    8: std::rt::lang_start
//    9: main
//   10: __libc_start_main
//   11: _start

标准库提供了 &str/String/Cow<’_, str> 到 Box<dyn Error> 的 From 转换:

impl<'a> From<&str> for Box<dyn Error + 'a>
impl<'a> From<&str> for Box<dyn Error + Sync + Send + 'a>
impl<'a, 'b> From<Cow<'b, str>> for Box<dyn Error + 'a>
impl<'a, 'b> From<Cow<'b, str>> for Box<dyn Error + Sync + Send + 'a>
impl<'a, E> From<E> for Box<dyn Error + 'a> where E: Error + 'a

impl<'a, E> From<E> for Box<dyn Error + Sync + Send + 'a>
  where E: Error + Send + Sync + 'a

impl<'a> From<String> for Box<dyn Error + 'a>
impl<'a> From<String> for Box<dyn Error + Sync + Send + 'a>

9 std::hash
#

std::hash module 提供了通用计算类型的 hash 值的支持。hash 值在 HashMap 和 HashSet 中广泛使用。

自定义类型需要先实现 std::hash::Hash trait, 然后才能使用 std::hash::Hasher 来计算 hash 值。

  • 对于任意实现了 Hash 和 Eq 类型, k1 == k2 -》hash(k1) == hash(k2) , 也就是如果 Eq 则 Hash 只需要相等,HashMap 和 HashSet 都依赖于这个语义;可以通过 #[derive(PartialEq, Eq, Hash)] 来确保这一点。
  • Rust 为绝大部分标准类似自动实现了 Hash trait, 如 str/String/Path/PathBuf/Cow 等。
pub trait Hash {
    // Required method
    fn hash<H>(&self, state: &mut H) where H: Hasher;

    // Provided method
    fn hash_slice<H>(data: &[Self], state: &mut H) where H: Hasher, Self: Sized
}

// Hash 一个基本类型
use std::hash::{DefaultHasher, Hash, Hasher};
let mut hasher = DefaultHasher::new();
7920.hash(&mut hasher);
println!("Hash is {:x}!", hasher.finish());

// Hash 一个 slice
let numbers = [6, 28, 496, 8128];
Hash::hash_slice(&numbers, &mut hasher);
println!("Hash is {:x}!", hasher.finish());

最简单的实现 Hash trait 的方式是使用 #[derive(Hash)]

use std::hash::{DefaultHasher, Hash, Hasher};

#[derive(Hash)]
struct Person {
    id: u32,
    name: String,
    phone: u64,
}
let person1 = Person {
    id: 5,
    name: "Janet".to_string(),
    phone: 555_666_7777,
};
let person2 = Person {
    id: 5,
    name: "Bob".to_string(),
    phone: 555_666_7777,
};

assert!(calculate_hash(&person1) != calculate_hash(&person2));

fn calculate_hash<T: Hash>(t: &T) -> u64 {
    let mut s = DefaultHasher::new();
    t.hash(&mut s); // t 实现了 Hash trait,故具有 hash() 方法,需要传入一个 Hasher
    s.finish() // 返回计算的 hash 值,是 u64 类型。
}

也可以手动实现 Hash trait:

use std::hash::{DefaultHasher, Hash, Hasher};

struct Person {
    id: u32,
    name: String,
    phone: u64,
}

impl Hash for Person {
    fn hash<H: Hasher>(&self, state: &mut H) {
        // Rust 已经为基本类型实现了 Hash trait,故可以挨个调用各 field 来计算 hash 值。
        // 注意:
        //  1. hash() 方法没有返回值;传入的 state 对应的 Hasher 内部维护有状态;
        //  2. 这里没有调用 state 的 finish() 方法,而是在后续对 Person 计算整体 hash 值时才调用该方法。
        self.id.hash(state);
        self.phone.hash(state);
    }
}

let person1 = Person {
    id: 5,
    name: "Janet".to_string(),
    phone: 555_666_7777,
};
let person2 = Person {
    id: 5,
    name: "Bob".to_string(),
    phone: 555_666_7777,
};

assert_eq!(calculate_hash(&person1), calculate_hash(&person2));

fn calculate_hash<T: Hash>(t: &T) -> u64 {
    let mut s = DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}

Trait std::hash::Hasher 定义了一个 Hash 算法类型需要实现的接口:

  • Hasher 内部包含状态 ,可以多次调用 Hasher 的 write/write_xx() 方法,最终调用 finish() 返回 hash 值;
pub trait Hasher {
    // Required methods
    fn finish(&self) -> u64;
    fn write(&mut self, bytes: &[u8]); // 计算任意字节序列的 hash 值。

    // Provided methods
    fn write_u8(&mut self, i: u8) { ... }
    fn write_u16(&mut self, i: u16) { ... }
    fn write_u32(&mut self, i: u32) { ... }
    fn write_u64(&mut self, i: u64) { ... }
    fn write_u128(&mut self, i: u128) { ... }
    fn write_usize(&mut self, i: usize) { ... }
    fn write_i8(&mut self, i: i8) { ... }
    fn write_i16(&mut self, i: i16) { ... }
    fn write_i32(&mut self, i: i32) { ... }
    fn write_i64(&mut self, i: i64) { ... }
    fn write_i128(&mut self, i: i128) { ... }
    fn write_isize(&mut self, i: isize) { ... }
    fn write_length_prefix(&mut self, len: usize) { ... }
    fn write_str(&mut self, s: &str) { ... } // 计算字符串 hash
}

// 举例
use std::hash::{DefaultHasher, Hasher};
let mut hasher = DefaultHasher::new();

hasher.write_u32(1989);
hasher.write_u8(11);
hasher.write_u8(9);
hasher.write(b"Huh?");
hasher.write(&[1, 2, 3, 4]);
hasher.write(&[5, 6]);

println!("Hash is {:x}!", hasher.finish());

std::hash::DefaultHasher 类型实现了 std::hash::Hasher trait,它是 HashMap 的 RandomState 的缺省实现。

由于 Hasher 内部包含状态(多次 write,最后 finish() 返回 hash 值),所以对于 HashMap 需要为每一个 key 创建一个 Hasher 来计算它的 hash 值。而 std::hash::BuildHasher trait 就是来创建该 Hasher 对象的:

  • 对于 build_hasher() 返回的 Hasher,相同的输入应该产生相同的 hash 值;
pub trait BuildHasher {
    type Hasher: Hasher;

    // Required method
    fn build_hasher(&self) -> Self::Hasher;

    // Provided method
    fn hash_one<T>(&self, x: T) -> u64
       where T: Hash,
             Self: Sized,
             Self::Hasher: Hasher { ... }
}

std::hash::RandomState struct 实现了 BuildHahser trait, 它是 HashMap 默认的 Hasher:

use std::hash::{BuildHasher, Hasher, RandomState};

let s = RandomState::new();
let mut hasher_1 = s.build_hasher();
let mut hasher_2 = s.build_hasher();

hasher_1.write_u32(8128);
hasher_2.write_u32(8128);

assert_eq!(hasher_1.finish(), hasher_2.finish());

创建 HashMap 时可以指定 Hasher:

use std::collections::HashMap;
use std::hash::RandomState;

let s = RandomState::new(); // RandomState 实现了 Trait std::hash::BuildHasher
let mut map = HashMap::with_hasher(s); // s 需要实现 Trait std::hash::BuildHasher
map.insert(1, 2);

10 std::convert
#

Enum std::convert::Infallible, 一般用于 Result 中的 error type, 表示不可能发生的 error(也就是 Result只能一直是 Ok), 这是通过该 enum 没有 variant value 来实现的。

pub enum Infallible {}

The error type for errors that can never happen . Since this enum has no variant, a value of this type can never actually exist. This can be useful for generic APIs that use Result and parameterize the error type, to indicate that the result is always Ok .

For example, the TryFrom trait (conversion that returns a Result) has a blanket implementation for all types where a reverse Into implementation exists.

impl<T, U> TryFrom<U> for T where U: Into<T> {
    type Error = Infallible;

    fn try_from(value: U) -> Result<Self, Infallible> {
        Ok(U::into(value))  // Never returns `Err`
    }
}

11 std::fmt
#

std::fmt module 提供的宏函数:

  • format! // described above
  • write! // first argument is either a &mut io::Write or a &mut fmt::Write, the destination
  • writeln! // same as write but appends a newline
  • print! // the format string is printed to the standard output
  • println! // same as print but appends a newline
  • eprint! // the format string is printed to the standard error
  • eprintln! // same as eprint but appends a newline
  • format_args! // described below.
format!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"

format!("{argument}", argument = "test");   // => "test"
format!("{name} {}", 1, name = 2);          // => "2 1"
format!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"

use std::fmt;
use std::io::{self, Write};
let mut some_writer = io::stdout();
write!(&mut some_writer, "{}", format_args!("print with a {}", "macro"));
fn my_fmt_fn(args: fmt::Arguments<'_>) {
    write!(&mut io::stdout(), "{args}");
}
my_fmt_fn(format_args!(", or a {} too", "function"));

可以使用 write!() 宏来实现 Display trait。

use std::io::Write;
let mut w = Vec::new();
let _ = write!(&mut w, "Hello {}!", "world"); // write!() 返回 std::io::Result, 不能忽略。

格式化:

  1. 字符串默认是左对齐,数字是右对齐
  2. 位置参数和命名参数可以混用,但命名参数必须位于位置参数后面;
  3. 命名参数的名称也可以是上下文中的变量名称;

语法:

format_string := text [ maybe_format text ] *
maybe_format := '{' '{' | '}' '}' | format
format := '{' [ argument ] [ ':' format_spec ] [ ws ] * '}'
argument := integer | identifier

format_spec := [[fill]align][sign]['#']['0'][width]['.' precision]type
fill := character
align := '<' | '^' | '>'
sign := '+' | '-'
width := count
precision := count | '*'
type := '' | '?' | 'x?' | 'X?' | identifier
count := parameter | integer
parameter := argument '$'

示例:

format!("Hello");                 // => "Hello"
format!("Hello, {}!", "world");   // => "Hello, world!"
format!("The number is {}", 1);   // => "The number is 1"
format!("{:?}", (3, 4));          // => "(3, 4)"
format!("{value}", value=4);      // => "4"
let people = "Rustaceans";
format!("Hello {people}!");       // => "Hello Rustaceans!"
format!("{} {}", 1, 2);           // => "1 2"
format!("{:04}", 42);             // => "0042" with leading zeros
format!("{:#?}", (100, 200));     // #? 表示 pretty print
// => "(
//       100,
//       200,
//     )"

format!("{name} {}", 1, name = 2);          // => "2 1"
format!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"

// width
// All of these print "Hello x    !"
println!("Hello {:5}!", "x");
println!("Hello {:1$}!", "x", 5);  // N$ 表示取第 N 个参数值为宽度,所以 1$ 取 5
println!("Hello {1:0$}!", 5, "x"); // 0$ 取值 5
println!("Hello {:width$}!", "x", width = 5); // width$ 取值变量 width
let width = 5;
println!("Hello {:width$}!", "x");

// align
assert_eq!(format!("Hello {:<5}!", "x"),  "Hello x    !");
assert_eq!(format!("Hello {:-<5}!", "x"), "Hello x----!");
assert_eq!(format!("Hello {:^5}!", "x"),  "Hello   x  !");
assert_eq!(format!("Hello {:>5}!", "x"),  "Hello     x!");

// Sign/#/0
assert_eq!(format!("Hello {:+}!", 5), "Hello +5!"); // + 显示正负号前缀
assert_eq!(format!("{:#x}!", 27), "0x1b!"); // #x 显示 0x 前缀
assert_eq!(format!("Hello {:05}!", 5),  "Hello 00005!"); // 0 表示补 0
assert_eq!(format!("Hello {:05}!", -5), "Hello -0005!");
assert_eq!(format!("{:#010x}!", 27), "0x0000001b!");

// 精度
//  .5 精度
println!("Hello {0} is {1:.5}", "x", 0.01);
// .N$ 表示取第 N 个位置参数值为精度
println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
println!("Hello {0} is {2:.1$}", "x", 5, 0.01);

// .* 有 3 种风格(建议使用 .N$ 风格):
// 1. {:.*} 表示: 下一个 position arg 为精度(5),后续为要打印的值(0.01)
println!("Hello {} is {:.*}",    "x", 5, 0.01);
// 2. {arg:.*} 表示:先取 arg 为要打印的值(0.01),再去下一个 position arg 为精度(5)
println!("Hello {1} is {2:.*}",  5, "x", 0.01);
println!("Hello {} is {2:.*}",   "x", 5, 0.01);
// 3. 直接指定两个参数
println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);

// 转义
assert_eq!(format!("Hello {{}}"), "Hello {}");
assert_eq!(format!("{{ Hello"), "{ Hello");

Fromat trait

  • nothing ⇒ Display
  • ? ⇒ Debug
  • x? ⇒ Debug with lower-case hexadecimal integers
  • X? ⇒ Debug with upper-case hexadecimal integers
  • o ⇒ Octal
  • x ⇒ LowerHex
  • X ⇒ UpperHex
  • p ⇒ Pointer
  • b ⇒ Binary
  • e ⇒ LowerExp
  • E ⇒ UpperExp

Debug 只能通过 #[derive(Debug)] 宏由编译器自动实现,而 Display trait 需要自己手动实现。

对于实现了 Display 的 trait ,Rust 自动为其实现 ToString trait ,它的 to_string() 方法返回一个 String。

  • 例如实现了 std::error:Error trait 时,同时需要实现 Display + Debug,所以所有 Error 对象都可以直接显示和生成字符串。
let i = 5;
let five = String::from("5");
assert_eq!(five, i.to_string());

12 std::mem
#

std::mem module 提供了各类型的 size/aligment/take/replace() 等操作函数:

  • align_of::<T>()/align_of_val(&v) 返回类型 T,或 v(需要传入借用类型) 指向的对象的对齐方式;
  • drop(T): drop 对象 T, 执行 T 实现的 Drop trait 方法;(不能直接调用对象实现的 Drop 的 drop() 方法);
  • forget(T): drop 对象 T, 但是不执行它的 Drop trait 方法;

replace/swap/take() 可以用来获取或替换 array/vec 中没有使用实现 Copy trait 的单个元素:

  • replace(&mut T1, T2): 用传入的 T2 值替换 &mut T1 对象,返回旧的 T1 值; 适用于替换不能 partial move 的对象。
  • swap(&mut T1, &mut T2): 交换 T1 和 T2 的值;
  • take(&mut T1): 返回 T1 的值,将原来 &mut T1 的值用 T1 的 Default 值填充
use std::mem;

pub const fn align_of<T>() -> usize
pub fn align_of_val<T>(val: &T) -> usize where T: ?Sized // 返回引用的 T 值类型的内存对齐要求
assert_eq!(4, mem::align_of::<i32>());
assert_eq!(4, mem::align_of_val(&5i32)); // 存入 val 的引用

// 回收 T 值(其实是获得 T 的所有权后丢弃)
pub fn drop<T>(_x: T)

// Takes ownership and “forgets” about the value without running its destructor.
pub const fn forget<T>(t: T)
let file = File::open("foo.txt").unwrap();
mem::forget(file);

// Moves src into the referenced dest, returning the previous dest value.
pub fn replace<T>(dest: &mut T, src: T) -> T
use std::mem;
let mut v: Vec<i32> = vec![1, 2];
// 新的值替换传入的 &mut 值 v,返回 v 对象。
let old_v = mem::replace(&mut v, vec![3, 4, 5]);
assert_eq!(vec![1, 2], old_v);
assert_eq!(vec![3, 4, 5], v);
// replace 的场景场景是替换容器中的元素
impl<T> Buffer<T> {
    fn replace_index(&mut self, i: usize, v: T) -> T {
        mem::replace(&mut self.buf[i], v) // 可以避免从 &mut self 中转移所有权而报错
    }
}

// 返回指定类型 T 的大小
pub const fn size_of<T>() -> usize
// Some primitives
assert_eq!(4, mem::size_of::<i32>());
assert_eq!(8, mem::size_of::<f64>());
assert_eq!(0, mem::size_of::<()>());
assert_eq!(8, mem::size_of::<[i32; 2]>());
assert_eq!(12, mem::size_of::<[i32; 3]>());
assert_eq!(0, mem::size_of::<[i32; 0]>());
assert_eq!(mem::size_of::<&i32>(), mem::size_of::<*const i32>());
assert_eq!(mem::size_of::<&i32>(), mem::size_of::<Box<i32>>());
assert_eq!(mem::size_of::<&i32>(), mem::size_of::<Option<&i32>>());
assert_eq!(mem::size_of::<Box<i32>>(), mem::size_of::<Option<Box<i32>>>());

//  返回借用指向的 T 的大小,和 size_of::<T>() 的区别是该方法也适用于动态类型大小,如 [T] 和 trait
//  object
pub fn size_of_val<T>(val: &T) -> usize where T: ?Sized
assert_eq!(4, mem::size_of_val(&5i32));
let x: [u8; 13] = [0; 13];
let y: &[u8] = &x;
assert_eq!(13, mem::size_of_val(y));

// Swaps the values at two mutable locations, without deinitializing either one.
pub fn swap<T>(x: &mut T, y: &mut T)
let mut x = 5;
let mut y = 42;
mem::swap(&mut x, &mut y);
assert_eq!(42, x);
assert_eq!(5, y);

// Replaces dest with the default value of T, returning the previous dest value.
pub fn take<T>(dest: &mut T) -> T where T: Default,
let mut v: Vec<i32> = vec![1, 2];
let old_v = mem::take(&mut v);
assert_eq!(vec![1, 2], old_v);
assert!(v.is_empty());

align_of/align_of_val() 以及 size_of/size_of_val() 都提供了 _val 版本,主要的使用场景是获得 动态类型 对应的实际类型的对齐方式或大小: 如 [T] 和 trait object。

std::mem::offset_of!() 宏返回 struct filed 或 enum variant field 的偏移量:

#![feature(offset_of_enum, offset_of_nested)]
#[repr(C)]
struct FieldStruct {
    first: u8,
    second: u16,
    third: u8
}
assert_eq!(mem::offset_of!(FieldStruct, first), 0);
assert_eq!(mem::offset_of!(FieldStruct, second), 2);
assert_eq!(mem::offset_of!(FieldStruct, third), 4);

#[repr(C)]
struct NestedA {
    b: NestedB
}
#[repr(C)]
struct NestedB(u8);
assert_eq!(mem::offset_of!(NestedA, b.0), 0);

#[repr(u8)]
enum Enum {
    A(u8, u16),
    B { one: u8, two: u16 },
}
assert_eq!(mem::offset_of!(Enum, A.0), 1);
assert_eq!(mem::offset_of!(Enum, B.two), 2);
assert_eq!(mem::offset_of!(Option<&u8>, Some.0), 0);

std::mem::discriminant() 返回 enum variant 的 tag 值,也可以使用 as 运算符来获取:

enum Foo {
    A(&'static str),
    B(i32),
    C(i32)
}
assert_eq!(mem::discriminant(&Foo::A("bar")), mem::discriminant(&Foo::A("baz")));
assert_eq!(mem::discriminant(&Foo::B(1)), mem::discriminant(&Foo::B(2)));
assert_ne!(mem::discriminant(&Foo::B(3)), mem::discriminant(&Foo::C(3)));

enum Enum {
    Foo,
    Bar,
    Baz,
}
assert_eq!(0, Enum::Foo as isize);
assert_eq!(1, Enum::Bar as isize);
assert_eq!(2, Enum::Baz as isize);

类型大小:

  1. () = 1;
  2. bool = 1;
  3. char = 4;
  4. *const T, &T, Box<T>, Option<&T>, 和 Option<Box<T>> 具有相同大小, 如果 T 是 Sized, 则这些类型大小和 usize 一致;
  5. [T; n]: 大小为 n * size_of::<T>()
  6. Enums: 如果 Enum 的各 variant 除了 tag 外都没有 data,则大小和 C enum 一致;
  7. Unions: 取决于最大 field 的大小;

对象类型的大小还受 #[repr(C)], repr(align(N)) 和 #[repr(u16)] 等属性的影响。

std::mem::transmute() :将 Src 类型的 value 解释为 Dst 类型的 value,这里的解释是 bit 级别 ,Src和 Dst类型 必须具有相同的长度 ,否则编译错误.

将 Src value bits 级别的 copy 为 Dst 的 value,然后 forget Src value(drop 但不调用它的 Drop trait)。

两个常用场景:

  1. 在 *const 指针和函数指针间转换;
  2. 扩充或缩短 lifetime;
pub const unsafe extern "rust-intrinsic" fn transmute<Src, Dst>(src: Src) -> Dst

let a: i64 = 42;
let a_ptr: *const i64 = &a as *const i64;
// 将 a_ptr 解释为 usize 后,可以对指针值执行算术运算
let a_addr: usize = unsafe {std::mem::transmute(a_ptr)};
println!("a: {} ({:p}...0x{:x})", a, a_ptr, a_addr + 7);

// transmute 的两个常用场景:
//
// 1. 在 *const 指针和函数指针间转换
fn foo() -> i32 { 0 }
// Crucially, we `as`-cast to a raw pointer before `transmute`ing to a function pointer.
// This avoids an integer-to-pointer `transmute`, which can be problematic.
// Transmuting between raw pointers and function pointers (i.e., two pointer types) is fine.
let pointer = foo as *const ();
let function = unsafe {
    std::mem::transmute::<*const (), fn() -> i32>(pointer)
};
assert_eq!(function(), 0);

// 2. 扩充或缩短 lifetime
struct R<'a>(&'a i32);
unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
    std::mem::transmute::<R<'b>, R<'static>>(r)
}
unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> {
    std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}

其它使用 transmute 场景,可以使用其它更安全的 APIs 来代替:

// 将 4 个 u8 解释为 u32
let raw_bytes = [0x78, 0x56, 0x34, 0x12];
let num = unsafe {
    std::mem::transmute::<[u8; 4], u32>(raw_bytes)
};
// use `u32::from_ne_bytes` instead
let num = u32::from_ne_bytes(raw_bytes);
// or use `u32::from_le_bytes` or `u32::from_be_bytes` to specify the endianness
let num = u32::from_le_bytes(raw_bytes);
assert_eq!(num, 0x12345678);
let num = u32::from_be_bytes(raw_bytes);
assert_eq!(num, 0x78563412);


// 将指针转为 usize
let ptr = &0;
let ptr_num_transmute = unsafe {
    std::mem::transmute::<&i32, usize>(ptr)
};
let ptr_num_cast = ptr as *const i32 as usize; // 使用更安全的 as 转换


// 将 *mut T 转换为 &mut T
let ptr: *mut i32 = &mut 0;
let ref_transmuted = unsafe {
    std::mem::transmute::<*mut i32, &mut i32>(ptr)
};
// Use a reborrow instead
let ref_casted = unsafe { &mut *ptr };

// 将 &mut T 转换为 &mut U:
let ptr = &mut 0;
let val_transmuted = unsafe {
    std::mem::transmute::<&mut i32, &mut u32>(ptr)
};
// Now, put together `as` and reborrowing - note the chaining of `as` `as` is not transitive
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };

// 将 &str 转换为 &[u8]
let slice = unsafe { std::mem::transmute::<&str, &[u8]>("Rust") };
assert_eq!(slice, &[82, 117, 115, 116]);
let slice = "Rust".as_bytes();
assert_eq!(slice, &[82, 117, 115, 116]);
assert_eq!(b"Rust", &[82, 117, 115, 116]);

pub const unsafe fn transmute_copy<Src, Dst>(src: &Src) -> DstInterprets src as having type &Dst, and then reads src without moving the contained value.

transmute_copy(&Src) -> Dst 函数是将 Src 类型解释为 Dst 类型,然后将 Src 的内容 byte copy 形成 Dst 类型对象,Src 和 Dst 不共享内存,也没有像 transmute() 那样要求 Src 和 Dst 大小必须一致:

use std::mem;

#[repr(packed)]
struct Foo {
    bar: u8,
}

let foo_array = [10u8];

unsafe {
    // Copy the data from 'foo_array' and treat it as a 'Foo'
    let mut foo_struct: Foo = mem::transmute_copy(&foo_array);
    assert_eq!(foo_struct.bar, 10);

    // Modify the copied data
    foo_struct.bar = 20;
    assert_eq!(foo_struct.bar, 20);
}

// The contents of 'foo_array' should not have changed
assert_eq!(foo_array, [10]);

std::mem::zeroed: pub const unsafe fn zeroed<T>() -> T :返回用 0 填充的 T 值(建议使用 std::mem::MaybeUninit::<T>::zerod()):

use std::mem;
let x: i32 = unsafe { mem::zeroed() };
assert_eq!(0, x);

// 错误的用法
let _x: &i32 = unsafe { mem::zeroed() }; // Undefined behavior!
let _y: fn() = unsafe { mem::zeroed() }; // And again!

Struct std::mem::ManuallyDrop<T> 获得 T 对象所有权,阻止编译器自动调用 T 的 Drop,而是在需要时手动 Drop 对象:

let mut x = std::mem::ManuallyDrop::new(String::from("Hello World!"));
x.truncate(5);
assert_eq!(*x, "Hello");
// But `Drop` will not be run here

12.1 MaybeUninit
#

一般情况下,Rust 编译器在创建对象时为其分配内存后会自动进行初始化,然后才能借用对象,否则就是 undefined behavior(对于未初始化的内存区域,每次 read 的结果可能不一致)。 比如在创建一个 bool 类型对象时,必须设置内存数据为 true 或 false,如果未初始化则 UB:

let x: &i32 = unsafe { mem::zeroed() }; // undefined behavior! ⚠
// The equivalent code with `MaybeUninit<&i32>`:
let x: &i32 = unsafe { MaybeUninit::zeroed().assume_init() }; // undefined behavior! ⚠

let b: bool = unsafe { mem::uninitialized() }; // undefined behavior!
// The equivalent code with `MaybeUninit<bool>`:
let b: bool = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior! ⚠

std::mem::MaybeUninit :使用 unsafe 代码来使用未初始化内存区域。编译器不会对 MaybeUninit<T> 进行 runtime tracking 和 safety check, Drop MaybeUninit<T> 对象时也不会调用 T 的 drop() 方法.

MaybeUninit<T> 实现的方法:

  • new(T):使用传入的 T 值来初始化内存;
  • uninit(): 为 T 类型分配一块未初始化内存;
  • uninit_array<const N: usize>():返回一个 N 个元素的未初始化数组内存,再将它转换为 slice 来使用;
  • pub const fn zeroed() -> MaybeUninit<T> : 返回一个用 zero 填充的对象 T;
  • write(value): 使用 value 设置 MaybeUninit<T> 内存数据,不 Drop 内存地址的数据(所有多次重复调用时可能导致内存泄露),返回 &mut T;
  • as_ptr/as_mut_ptr(): 转换为 raw pointer,可以使用它的 read/write 等方法来读写内存;
  • assume_init(self) -> T:返回内存保存的 T 类型值=( =转移 该内存区域所有权);
  • assume_init_read(&self) -> T:从内存区域读取各 bitwise copy(不管 T 类型是否实现 Copy)来构建一个 T 类型值(内部实现调用的是 std::ptr::read() 函数实现的 浅拷贝 ,共享同一个内存区域 )。
    • 由于多次调用 assume_init_read() 返回的 T 共享同一个内存区域 ,当各 T 被 Drop 时会导致 多次内存释放的错误 ,所以建议使用 assume_init() 来返回包含对应内存区域所有权的 T 值,从而避免多次释放。
pub union MaybeUninit<T> {
    /* private fields */
}

impl<T> MaybeUninit<T>

// Creates a new MaybeUninit<T> initialized with the given value. It is safe to call assume_init
// on the return value of this function.

// Note that dropping a MaybeUninit<T> will never call T’s drop code. It is your responsibility to
// make sure T gets dropped if it got initialized.
pub const fn new(val: T) -> MaybeUninit<T>
use std::mem::MaybeUninit;
let v: MaybeUninit<Vec<u8>> = MaybeUninit::new(vec![42]);

// Creates a new MaybeUninit<T> in an uninitialized state.
pub const fn uninit() -> MaybeUninit<T>
use std::mem::MaybeUninit;
let v: MaybeUninit<String> = MaybeUninit::uninit();

pub fn uninit_array<const N: usize>() -> [MaybeUninit<T>; N]
#![feature(maybe_uninit_uninit_array, maybe_uninit_slice)]
use std::mem::MaybeUninit;
extern "C" {
    fn read_into_buffer(ptr: *mut u8, max_len: usize) -> usize;
}
/// Returns a (possibly smaller) slice of data that was actually read
fn read(buf: &mut [MaybeUninit<u8>]) -> &[u8] {
    unsafe {
        let len = read_into_buffer(buf.as_mut_ptr() as *mut u8, buf.len());
        MaybeUninit::slice_assume_init_ref(&buf[..len])
    }
}
let mut buf: [MaybeUninit<u8>; 32] = MaybeUninit::uninit_array();
let data = read(&mut buf);


// Creates a new MaybeUninit<T> in an uninitialized state, with the memory being filled with 0
// bytes.
pub const fn zeroed() -> MaybeUninit<T>
let x = MaybeUninit::<(u8, bool)>::zeroed();
let x = unsafe { x.assume_init() };
assert_eq!(x, (0, false));


pub fn write(&mut self, val: T) -> &mut T
let mut x = MaybeUninit::<Vec<u8>>::uninit();
{
    let hello = x.write((&b"Hello, world!").to_vec());
    // Setting hello does not leak prior allocations, but drops them
    *hello = (&b"Hello").to_vec();
    hello[0] = 'h' as u8;
}
// x is initialized now:
let s = unsafe { x.assume_init() };
assert_eq!(b"hello", s.as_slice());


// Gets a pointer to the contained value. Reading from this pointer or turning it into a reference
// is undefined behavior unless the MaybeUninit<T> is initialized.
pub const fn as_ptr(&self) -> *const T
pub fn as_mut_ptr(&mut self) -> *mut T
use std::mem::MaybeUninit;
let mut x = MaybeUninit::<Vec<u32>>::uninit();
x.write(vec![0, 1, 2]);
// Create a reference into the `MaybeUninit<T>`. This is okay because we initialized it.
let x_vec = unsafe { &*x.as_ptr() };
assert_eq!(x_vec.len(), 3);

// Extracts the value from the MaybeUninit<T> container. This is a great way to ensure that the
// data will get dropped, because the resulting T is subject to the usual drop handling.
pub const unsafe fn assume_init(self) -> T // 转移所有权,返回 T
pub const unsafe fn assume_init_read(&self) -> T // 转移所有权
pub unsafe fn assume_init_drop(&mut self) // in-place Drop 包含的值
pub const unsafe fn assume_init_ref(&self) -> &T // 返回 &T,不转移所有权
pub unsafe fn assume_init_mut(&mut self) -> &mut T // 返回 &mut T
pub unsafe fn array_assume_init<const N: usize>( array: [MaybeUninit<T>; N] ) -> [T; N] // 返回 array
pub unsafe fn slice_assume_init_ref(slice: &[MaybeUninit<T>]) -> &[T] // 返回 slice: &[T]
pub unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit<T>]) -> &mut [T] // 返回 mut slice: &mut [T]
let mut x = MaybeUninit::<bool>::uninit();
x.write(true);
let x_init = unsafe { x.assume_init() };
assert_eq!(x_init, true);


pub fn slice_as_ptr(this: &[MaybeUninit<T>]) -> *const T
pub fn slice_as_mut_ptr(this: &mut [MaybeUninit<T>]) -> *mut T

pub fn write_slice<'a>(this: &'a mut [MaybeUninit<T>], src: &[T]) -> &'a mut [T] where T: Copy
pub fn write_slice_cloned<'a>( this: &'a mut [MaybeUninit<T>], src: &[T] ) -> &'a mut [T] where T: Clone

pub fn as_bytes(&self) -> &[MaybeUninit<u8>]
pub fn as_bytes_mut(&mut self) -> &mut [MaybeUninit<u8>]
pub fn slice_as_bytes(this: &[MaybeUninit<T>]) -> &[MaybeUninit<u8>]
pub fn slice_as_bytes_mut(this: &mut [MaybeUninit<T>]) -> &mut [MaybeUninit<u8>]

MaybeUninit<T> 为 T 分配未初始化的内存,拥有该内存区域, 但不做任何优化和操作:

  • 先使用 as_ptr()/as_mut_ptr() 转换为 raw pointer,将它传递给 FFI 函数使用;
    • C 很常见的情况是:给函数传递一个内存指针, 让函数内的逻辑来修改指针指向的内容。
  • 或者使用 MaybeUninit::write() 或者 raw pointer 的 read/write() 来对未初始化内存区域进行读写;
  • 然后调用 MaybeUninit.assume_init() 来将内存区域标记为已初始化,返回类型 T 的值;
let mut x = MaybeUninit::<&i32>::uninit(); // 为 &i32 分配内存,但是不初始化
x.write(&0); // 使用 MaybeUninit 类型的 write() 方法写入数据
let x = unsafe { x.assume_init() };  // 提取数据,只有当对 x 做过初始化处理后才有意义

// undefined behavior 的情况:x 未被初始化,assume_init() 返回的 x 值是 UB 的。
let x = MaybeUninit::<Vec<u32>>::uninit();
let x_init = unsafe { x.assume_init() };


// 传入未初始化内存区域的 raw pointer,然后使用它的 write() 方法来填充内存区域的值
unsafe fn make_vec(out: *mut Vec<i32>) {
    // `write` does not drop the old contents, which is important.
    out.write(vec![1, 2, 3]);
}
// 未指定类型,由编译器自动推导。
let mut v = MaybeUninit::uninit();
unsafe { make_vec(v.as_mut_ptr()); }
let v = unsafe { v.assume_init() };
assert_eq!(&v, &[1, 2, 3]);


// 使用 MaybeUninit 来初始化 array elem:
let data = {
    // Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the type
    // we are claiming to have initialized here is a bunch of `MaybeUninit`s, which do not require
    // initialization.
    let mut data: [MaybeUninit<Vec<u32>>; 1000] = unsafe {
        MaybeUninit::uninit().assume_init()
    };
    for elem in &mut data[..] {
        elem.write(vec![42]);
    }
    // Everything is initialized. Transmute the array to the initialized type.
    unsafe { mem::transmute::<_, [Vec<u32>; 1000]>(data) }
};
assert_eq!(&data[0], &[42]);


// 使用 MaybeUninit 来初始化 struct field
#[derive(Debug, PartialEq)]
pub struct Foo {
    name: String,
    list: Vec<u8>,
}
let foo = {
    let mut uninit: MaybeUninit<Foo> = MaybeUninit::uninit();
    let ptr = uninit.as_mut_ptr();

    // Initializing the `name` field
    // Using `write` instead of assignment via `=` to not call `drop` on the
    // old, uninitialized value.
    unsafe { std::ptr::addr_of_mut!((*ptr).name).write("Bob".to_string()); }

    // Initializing the `list` field
    // If there is a panic here, then the `String` in the `name` field leaks.
    unsafe { std::ptr::addr_of_mut!((*ptr).list).write(vec![0, 1, 2]); }
    unsafe { uninit.assume_init() }
};
assert_eq!( foo, Foo { name: "Bob".to_string(), list: vec![0, 1, 2]});

MaybeUninit<T>: 保证和 T 同样的大小、对齐和 ABI:

use std::mem::{MaybeUninit, size_of, align_of};

assert_eq!(size_of::<MaybeUninit<u64>>(), size_of::<u64>());
assert_eq!(align_of::<MaybeUninit<u64>>(), align_of::<u64>());
assert_eq!(size_of::<Option<bool>>(), 1);
assert_eq!(size_of::<Option<MaybeUninit<bool>>>(), 2);

13 std::ptr
#

std::ptr module 提供了一些操作 raw pointer 的函数。raw pointer 类型 *const T 或 *mut T 本身也提供一些方法,也可以用来操作 raw pointer。

这个 module 中通过 raw pointer 如 *mut T 或 *const T 来存取一个值,这个值的大小如果没有特殊说明,对应的是 std::mem::size_of::<T>() bytes。

使用 std::ptr::addr_of!() 和 std::ptr::addr_of_mut!() 来返回 struct field 的地址:

  • packed struct:默认情况下,struct 对象的 field 会通过 pading 来对齐。通过添加 packed attr,可以关闭 struct field padding 对齐机制,这样 struct 的某个 field 可能是未对齐的。
  • 对于未对齐的 filed,是不能创建引用的,但是通过 addr_of!() 和 addr_of_mut!() 宏是可以创建 未对齐的 raw pointer
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, packed)]
struct S {
    aligned: u8,
    unaligned: u32,
}
let s = S::default();
let p = std::ptr::addr_of!(s.unaligned); // not allowed with coercion

Rust 的 raw pointer 和引用都是指针,包括两部分:

  1. data pointer:指向 value 内存地址的指针;
  2. 可选的 metadata 指针;

对于编译时可知的固定大小类型(实现了 Sized trait)或 extern 类型的指针,是 thin 指针 ,它的 metadata 是zero-sized 的 () 类型,所以 thin 指针是只占用一个机器字 usize 的变量。

对于动态大小类型(DST),它的指针是 fat 指针 ,它的 metadata 是非空的。fat 指针占用两个 usize 大小(data pointer + metadata pointer),如 *const [u8] 或 *mut dyn std::io::Write:

  • For structs whose last field is a DST, metadata is the metadata for the last field
  • For the str type, metadata is the length in bytes as usize
  • For slice types like [T], metadata is the length in items as usize
  • For trait objects like dyn SomeTrait, metadata is DynMetadata<Self> (e.g. DynMetadata<dyn SomeTrait>)

std::ptr::Pointee trait 来为任意指针(thin 或 fat pointer)提供 metadata 的 type 信息:

  • Metadata 关联类型可能是 () or usize or DynMetadata<_> 类型;
    • (): 对应没有 Metadata 的 thin 指针;
    • usize:对应 lenght in bytes(如 &str)或 length in items(如 [T]);
    • DynMetadata: 对应 trait object;
  • Rust 为所有类型实现了该 trait ,所以可以直接使用;
  • raw pointer 的 .to_raw_parts() 方法返回对象的 data pointer 和包裹 Metadata 类型的 Pointee 对象;
  • std::ptr::metadata() 方法返回对象的 Metadata 类型对象;
  • std::ptr::fromm_raw_parts()/from_raw_parts_mut() 函数来使用 data pointer 和 Metadata 类型对象创建 raw pointer
// raw pointer 的 to_raw_parts() 方法返回他的 Metadata 信息
pub trait Pointee {
    // Metadata 的实例化类型:() 或 usize 或 DynMetadata<Dyn>
    type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}

pub struct DynMetadata<Dyn> where Dyn: ?Sized,
{ /* private fields */ }

// Decompose a (possibly wide) pointer into its data pointer and metadata components.
pub fn to_raw_parts(self) -> (*const (), <T as Pointee>::Metadata)

// Forms a (possibly-wide) raw pointer from a data pointer and metadata.
pub fn from_raw_parts<T>(data_pointer: *const (), metadata: <T as Pointee>::Metadata) -> *const T where T: ?Sized,

Struct std::ptr::DynMetadata 是 trait object 的 metadata,It is a pointer to a vtable (virtual call table) that represents all the necessary information to manipulate the concrete type stored inside a trait object. The vtable notably it contains:

  • type size
  • type alignment
  • a pointer to the type’s drop_in_place impl (may be a no-op for plain-old-data)
  • pointers to all the methods for the type’s implementation of the trait

Rust 的 type coercion 为引用和 raw pointer 提供了隐式自动转换(也可以使用 as 运算符来对 type coercion 显式转换): https://doc.rust-lang.org/stable/reference/type-coercions.html

  1. &mut T to &T <- 可变引用可以转换为不可变引用
  2. *mut T to *const T <– 可变 raw pointer 可以转换为不可变 raw pointer
  3. &T to *const T <– 不可变引用 可以转换为 不可变 raw pointer
  4. &mut T to *mut T <– 可变引用 可以转换为 可变 raw pointer
// &mut T -> &T
let r: &i32 = &mut 1i32;

// &T -> *const T
let rp: *const i32 = &1i32;

// &mut T -> *const T 或 *mut T
let r: *const i32 = &mut 1i32;
let r: *mut i32 = &mut 1i32;

// *mut T -> *const T
// 可以使用 as 运算符, 对支持 type coercion 的变量类型进行转换.
let rp: *const i32 = &mut 1i32 as *mut i32 as *const i32;

std::ptr module 提供的函数列表(大部分函数也是 *const T 或 *mut T 类型的方法):

  • addr_eq Compares the addresses of the two pointers for equality, ignoring any metadata in fat pointers.
  • copy Copies count * size_of::<T>() bytes from src to dst. The source and destination may overlap.
  • copy_nonoverlapping Copies count * size_of::<T>() bytes from src to dst. The source and destination must not overlap.
  • drop_in_place Executes the destructor (if any) of the pointed-to value.
  • eq Compares raw pointers for equality.
  • from_mut Convert a mutable reference to a raw pointer.
  • from_ref Convert a reference to a raw pointer.
  • hash Hash a raw pointer.
  • null Creates a null raw pointer.
  • null_mut Creates a null mutable raw pointer.
  • read Reads the value from src without moving it. This leaves the memory in src unchanged.
  • read_unaligned Reads the value from src without moving it. This leaves the memory in src unchanged.
  • read_volatile Performs a volatile read of the value from src without moving it. This leaves the memory in src unchanged.
  • replace Moves src into the pointed dst, returning the previous dst value.
  • slice_from_raw_parts Forms a raw slice from a pointer and a length.
  • slice_from_raw_parts_mut Performs the same functionality as slice_from_raw_parts, except that a raw mutable slice is returned, as opposed to a raw immutable slice.
  • swap⚠ Swaps the values at two mutable locations of the same type, without deinitializing either.
  • swap_nonoverlapping⚠ Swaps count * size_of::<T>() bytes between the two regions of memory beginning at x and y. The two regions must not overlap.
  • write Overwrites a memory location with the given value without reading or dropping the old value.
  • write_bytes Sets count * size_of::<T>() bytes of memory starting at dst to val.
  • write_unaligned Overwrites a memory location with the given value without reading or dropping the old value.
  • write_volatile Performs a volatile write of a memory location with the given value without reading or dropping the old value.
  • from_exposed_addrExperimental Convert an address back to a pointer, picking up a previously ‘exposed’ provenance.
  • from_exposed_addr_mutExperimental Convert an address back to a mutable pointer, picking up a previously ‘exposed’ provenance.
  • from_raw_partsExperimental Forms a (possibly-wide) raw pointer from a data pointer and metadata.
  • from_raw_parts_mutExperimental Performs the same functionality as from_raw_parts, except that a raw *mut pointer is returned, as opposed to a raw *const pointer.
  • invalidExperimental Creates an invalid pointer with the given address.
  • invalid_mutExperimental Creates an invalid mutable pointer with the given address.
  • metadataExperimental Extract the metadata component of a pointer.

std::ptrread()/write() 函数:

  1. std::ptr::read(): pub const unsafe fn read<T>(src: *const T) -> T ,创建 src 的一个 bitwise copy 值(返回的 T 和 src 指向 同一个内存区域 ),而不管 T 是否实现了 Copy,不影响和移动 src 内存中的内容。同时 src 和返回的 T 值内存是独立的。
    • 如果 T 没有实现 Copy,则 src 和返回的 T 由于指向同一个内存区域,可能会出现双重释放的问题。
  2. std::ptr::write(): pub unsafe fn write<T>(dst: *mut T, src: T) ,write() can be used to overwrite data without causing it to be dropped. 不会 drop dst 内容,同时将 src 所有权转移到dst
let x = 12;
let y = &x as *const i32;
unsafe {
    assert_eq!(std::ptr::read(y), 12);
}


use std::ptr;
fn swap<T>(a: &mut T, b: &mut T) {
    unsafe {
        // Create a bitwise copy of the value at `a` in `tmp`.
        let tmp = ptr::read(a);

        // Exiting at this point (either by explicitly returning or by calling a function which
        // panics) would cause the value in `tmp` to be dropped while the same value is still
        // referenced by `a`. This could trigger undefined behavior if `T` is not `Copy`.

        // Create a bitwise copy of the value at `b` in `a`.  This is safe because mutable
        // references cannot alias.
        ptr::copy_nonoverlapping(b, a, 1);

        // As above, exiting here could trigger undefined behavior because the same value is
        // referenced by `a` and `b`.

        // Move `tmp` into `b`.
        ptr::write(b, tmp);

        // `tmp` has been moved (`write` takes ownership of its second argument), so nothing is
        // dropped implicitly here.

        // tmp 和 a 虽然指向同一个内存区域,但由于 tmp 已经被 write() 转移所有权,所以这里不会释放 tmp。
        // 不会有双重释放的问题。
    }
}
let mut foo = "foo".to_owned();
let mut bar = "bar".to_owned();
swap(&mut foo, &mut bar);
assert_eq!(foo, "bar");
assert_eq!(bar, "foo");

*src = value; 会 Drop src 处的值,而 std::ptr::write() 可以在重写值的同时不 Drop 原来的值。

use std::ptr;

let mut s = String::from("foo");
unsafe {
    // s2 和 s 共享相同的内存区域
    let mut s2: String = ptr::read(&s);
    assert_eq!(s2, "foo");

    // 向 s2 赋值时,会 Drop 对应内存区域的原始值,所以 s 不能再使用(因其内存已经被释放)
    s2 = String::default();
    assert_eq!(s2, "");

    // Assigning to `s` would cause the old value to be dropped again, resulting in undefined behavior.
    // s = String::from("bar"); // ERROR

    // ptr::write() 覆盖 s 中的值但不 Drop 它,所以 OK。
    ptr::write(&mut s, String::from("bar"));
}
assert_eq!(s, "bar");

异常的情况:s 和 spr 都指向同一个内存区域, 会导致双重释放 ,从而出错。

let mut s = "abcd".to_string();
let sp = &mut s as *mut String;
let mut spr = unsafe {std::ptr::read(sp)};
spr.push_str(" test");
println!("s {}, spr {}", s, spr);

// 报错:
// s abcd, spr abcd test
// foo(85413,0x1f2d40f40) malloc: *** error for object 0x600003238060: pointer being freed was not allocated
// foo(85413,0x1f2d40f40) malloc: *** set a breakpoint in malloc_error_break to debug

std::ptr::write() 的注意点:

  1. 不会 drop dst,有可能导致 dst 指向的内存区域内存泄露;
  2. 不会 drop src,而是将 src 所有权转移到 dst。

14 std::process
#

std::process module 提供的函数:

  • abort : Terminates the process in an abnormal fashion.
  • exit : Terminates the current process with the specified exit code.
  • id : Returns the OS-assigned process identifier associated with this process.

std::process::Command::new() 创建一个 Command, 默认的行为如下:

  1. No arguments to the program
  2. Inherit the current process’s environment
  3. Inherit the current process’s working directory
  4. Inherit stdin/stdout/stderr for spawn() or status(), but create pipes for output()
    • 对于 spawn()/status() 默认是继承父进程的 stdin/stdout/stderr;
    • 对于 output() 则会创建 pipe,这样后续可以获得 Output 对象的 stdout 和 stderr。

在执行 Command 前, 可以进一步设置子进程的参数,工作目录,环境变量等:

// 创建要执行的 program
pub fn new<S: AsRef<OsStr>>(program: S) -> Command

// 设置 program 参数
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command // 一次只能设置一个参数
pub fn args<I, S>(&mut self, args: I) -> &mut Command where I: IntoIterator<Item = S>, S: AsRef<OsStr>,

// 设置和清理环境变量
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Command where K: AsRef<OsStr>, V: AsRef<OsStr>,
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Command
where
    I: IntoIterator<Item = (K, V)>,
    K: AsRef<OsStr>,
    V: AsRef<OsStr>,
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Command
pub fn env_clear(&mut self) -> &mut Command

// 设置工作目录
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Command

// 设置子进程的 stdin/stdout/stderr
//
// cfg 是可以转换为 Stdio 的类型, 例如 Stdio::null(), Stdio::inherit(), Stdio::piped(),
// Stdio::from()
//
// 对于 spawn()/status() 默认是 Stdio::inherit(), 对于 output() 默认是 Stdio::piped(), 所以对于
// output() 后续可以读取它的 stdout/stderr field 来获得 Command 的输出。
//
pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command
pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command

// 获取进程信息
pub fn get_program(&self) -> &OsStr
pub fn get_args(&self) -> CommandArgs<'_>
pub fn get_envs(&self) -> CommandEnvs<'_>
pub fn get_current_dir(&self) -> Option<&Path>

// 上面创建的 Command 可以重复执行来进行复用。

// 执行 Command 并获得输出或退出状态:
// 1. spawn() 立即返回一个 Child,调用 kill/wait/wait_with_output() 结束;
// 2. 直接运行直到结束后返回: output/status();
pub fn spawn(&mut self) -> Result<Child>
pub fn output(&mut self) -> Result<Output> // Output 中包含 status/stdout/stderr 内容
pub fn status(&mut self) -> Result<ExitStatus>

// Linux/Unix 的 CommandExt 方法:
fn create_pidfd(&mut self, val: bool) -> &mut Command
fn uid(&mut self, id: u32) -> &mut Command
fn gid(&mut self, id: u32) -> &mut Command
fn groups(&mut self, groups: &[u32]) -> &mut Command
fn process_group(&mut self, pgroup: i32) -> &mut Command
fn exec(&mut self) -> Error // 准备好所有工作, 执行 execvp 系统调用
fn arg0<S>(&mut self, arg: S) -> &mut Command // 为命令指定 arg0 参数,即 command name
unsafe fn pre_exec<F>(&mut self, f: F) -> &mut Command where F: FnMut() -> Result<()> + Send + Sync + 'static
fn before_exec<F>(&mut self, f: F) -> &mut Command where F: FnMut() -> Result<()> + Send + Sync + 'static

// 举例
use std::process::Command;

let output = if cfg!(target_os = "windows") {
    Command::new("cmd")
        .args(["/C", "echo hello"])
        .output()
        .expect("failed to execute process")
} else {
    Command::new("sh")
        .arg("-c")
        .arg("echo hello")
        .args(["-l", "-a"])
	.current_dir("/bin")
        .env_remove("PATH")
	.env_clear()
        .env("PATH", "/bin")
        .stdin(Stdio::null())
        .stdout(Stdio::inherit())
        .stderr(Stdio::null())
        .output() // 立即执行命令并捕获输出,等待命令执行结束后返回
        .expect("failed to execute process")
};
let hello = output.stdout;

use std::process::Command;
use std::io::{self, Write};
let output = Command::new("/bin/cat")
    .arg("file.txt")
    .output()
    .expect("failed to execute process");
println!("status: {}", output.status);
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
assert!(output.status.success());

spawn 产生的 Child 子进程并不等待子进程执行结束, 后续需要使用 wait/try_wait/wait_with_output() 等方法来等待执行结束, 或者 kill() 来终止子进程。Child 类型没有实现 Drop trait,所以需要自己确保 Child 进程运行结束。

pub struct Child {
    pub stdin: Option<ChildStdin>, // 实现了 Write
    pub stdout: Option<ChildStdout>, // 实现了 Read
    pub stderr: Option<ChildStderr>, // 实现了 Read
    /* private fields */
}
pub fn id(&self) -> u32

// 等待 Child 执行结束(try_wait() 只是检查,不 block)
pub fn kill(&mut self) -> Result<()>
pub fn wait(&mut self) -> Result<ExitStatus> // 等待直到程序退出
pub fn try_wait(&mut self) -> Result<Option<ExitStatus>> // 不 block 调用线程,而是检查程序是否退出
// 等待程序结束并捕获所有的 stdout/stderr,需要事先通过 Comand.stdin()/stdout() 设置为 piped 才行。
pub fn wait_with_output(self) -> Result<Output>

// 例子
use std::process::{Command, Stdio};
use std::io::Write;
let mut child = Command::new("/bin/cat")
    .stdin(Stdio::piped()) // 设置 Child 的 stdin
    .stdout(Stdio::piped())
    .spawn()
    .expect("failed to execute child");
let mut stdin = child.stdin.take().expect("failed to get stdin");
std::thread::spawn(move || {
    stdin.write_all(b"test").expect("failed to write to stdin");
});
let output = child
    .wait_with_output()
    .expect("failed to wait on child");
assert_eq!(b"test", output.stdout.as_slice());

Child 子进程的输入/输出可以通过 Child struct 的 stdin/stdout/stderr field 来获取, 通过读写它们来获得子进程的输入/输出;

  • Child 的 stdin/stdout/stderr 是通过 Command 的 stdin/stdout/stderr() 方法来设置;
  • 在使用 child.stdin/stdout/stderr 时,一般使用 let mut stdin = child.stdin.take().except("xxx") , take() 方法将获取 stdin/stdout/stderr 的所有权,这样当返回的对象被 Drop 时, 相应的文件描述符被关闭
use std::process::{Command, Stdio};

let echo_child = Command::new("echo")
    .arg("Oh no, a tpyo!")
    .stdout(Stdio::piped())
    .spawn()
    .expect("Failed to start echo process");
let echo_out = echo_child.stdout.expect("Failed to open echo stdout");

let mut sed_child = Command::new("sed")
    .arg("s/tpyo/typo/")
    .stdin(Stdio::from(echo_out)) // 从 echo_child 获得输入;
    .stdout(Stdio::piped())
    .spawn()
    .expect("Failed to start sed process");
let output = sed_child.wait_with_output().expect("Failed to wait on sed");
assert_eq!(b"Oh no, a typo!\n", output.stdout.as_slice());

std::process::Stdio 可以作为 Command 的 stdin()/stdout()/stderr() 的输入,来源多样:

impl Stdio

// 将 child 进程的 stdin/stdout/stderr Pipeline 输出到 Child 对象的 stdin/stdout/stderr filed 中,
// 这样父进程(如创建 Command 的主进程)就可以对它们进行读写。
pub fn piped() -> Stdio

// child 从 parent 继承
pub fn inherit() -> Stdio

// 关闭
pub fn null() -> Stdio

// 判断 Stdio 是否是 piped() 生成的.
pub fn makes_pipe(&self) -> bool

// 从其它 Child 的 stdin/stdout/stderr 来创建 Stdio, 从而实现 Child 之间的串联
impl From<ChildStderr> for Stdio
impl From<ChildStdin> for Stdio
impl From<ChildStdout> for Stdio

// 读写文件
impl From<File> for Stdio

// 从终端读写
impl From<Stderr> for Stdio // std::io::Stderr
impl From<Stdout> for Stdio // std::io::Stdout

对于 Unix 系统,ExitStatus 还实现了 ExitStatusExt trait,可以返回进程因为 signal 而终止的情况:

pub trait ExitStatusExt: Sealed {
    // Required methods
    fn from_raw(raw: i32) -> Self;
    fn signal(&self) -> Option<i32>; // If the process was terminated by a signal, returns that
                                     // signal.
    fn core_dumped(&self) -> bool;
    fn stopped_signal(&self) -> Option<i32>; // If the process was stopped by a signal, returns
                                             // that signal.
    fn continued(&self) -> bool;
    fn into_raw(self) -> i32;
}

示例:

use std::process::{Command, Stdio};
let output = Command::new("echo")
    .arg("Hello, world!")
    .stdout(Stdio::piped())  // Command 子进程的 stdout 被 pipe 到 output.stdout
    .output()
    .expect("Failed to execute command");
// 从 output.stdout 获取子进程输出
assert_eq!(String::from_utf8_lossy(&output.stdout), "Hello, world!\n");
// Nothing echoed to console

use std::process::{Command, Stdio};
let output = Command::new("echo")
    .arg("Hello, world!")
    .stdout(Stdio::inherit()) // 继承父进程 stdout,默认被打到终端
    .output()
    .expect("Failed to execute command");
// stdout 被打印到 console, 所以 Child.stdout 为空
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
// "Hello, world!" echoed to console

use std::fs::File;
use std::process::Command;
let file = File::open("foo.txt").unwrap();
let reverse = Command::new("rev")
    .stdin(file)  // 从文件中读取输入
    .output()
    .expect("failed reverse command");
assert_eq!(reverse.stdout, b"!dlrow ,olleH");


use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("rev")
    .stdin(Stdio::piped()) // 创建一个输入 pipe, Command 从它读取内容
    .stdout(Stdio::piped())
    .spawn()
    .expect("Failed to spawn child process");

// 父进程向 stdin 发送数据, 被 child 接收child.stdin 是 Option<ChildStdin> 类型,其中 ChildStdin
// 实现了 Write trait,child.stdin.take() 获得 ChildStdin 的所有权,当它被 Drop 时关闭 stdin。
let mut stdin = child.stdin.take().expect("Failed to open stdin");
std::thread::spawn(move || {
    stdin.write_all("Hello, world!".as_bytes()).expect("Failed to write to stdin");
});
let output = child.wait_with_output().expect("Failed to read stdout");
assert_eq!(String::from_utf8_lossy(&output.stdout), "!dlrow ,olleH");

Stdio 可以从 std::io 的 File/Stderr/Stdout 和其它 Child 的 std::process::ChildStderr/ChildStdin/ChildStdout 来创建:

impl From<ChildStderr> for Stdio
impl From<ChildStdin> for Stdio
impl From<ChildStdout> for Stdio
impl From<File> for Stdio
impl From<Stderr> for Stdio // std::io::Stderr
impl From<Stdout> for Stdio // std::io::Stdout


// 将一个 Child Command 的 stdin/stdout/stderr 连接到其他 Child Command
use std::process::{Command, Stdio};
let hello = Command::new("echo")
    .arg("Hello, world!")
    .stdout(Stdio::piped()) // 必须 piped,后续才能从 Child 的 stdout field 获取到
    .spawn()
    .expect("failed echo command");

let reverse = Command::new("rev")
    .stdin(hello.stdout.unwrap())  // Converted into a Stdio here。注:Option 的 unwrap() 方法返回 T
    .output()
    .expect("failed reverse command");
assert_eq!(reverse.stdout, b"!dlrow ,olleH\n");


// 将 Command 的输入输出连接到 File
use std::fs::File;
use std::process::Command;
// With the `foo.txt` file containing "Hello, world!"
let file = File::open("foo.txt").unwrap();
let reverse = Command::new("rev")
    .stdin(file)  // Implicit File conversion into a Stdio
    .output()
    .expect("failed reverse command");
assert_eq!(reverse.stdout, b"!dlrow ,olleH");


// 将 Command 的 stdout/stderr 连接到 std::io::Stdout/Stderr
#![feature(exit_status_error)]
use std::process::Command;
let output = Command::new("whoami")
    .stdout(std::io::stdout())
    .stderr(std::io::stderr())
    .output()?;
output.status.exit_ok()?;
assert!(output.stdout.is_empty());

15 std::io
#

std::io module 返回通用错误 std::io::Result<T> ,它是 std::io::Result<T, std::io::Error> 的别名。

std::io::Error 是实现了 std::error::Error trait 的 struct 类型,它的 kind() 方法返回 std::io::ErrorKind 枚举类型,可以用来进一步判断错误的原因:

use std::io::{Error, ErrorKind};

fn print_error(err: Error) {
    println!("{:?}", err.kind());
}

fn main() {
    print_error(Error::last_os_error());
    print_error(Error::new(ErrorKind::AddrInUse, "oh no!"));
}

std::io::prelude 包含了 std::io module 中常用的 trait 和类型,一般需要提前 use 导入。

Structs

BufReader
The BufReader<R> struct adds buffering to any reader.
BufWriter
Wraps a writer and buffers its output.
Bytes
An iterator over u8 values of a reader.
Chain
Adapter to chain together two readers.
Cursor
A Cursor wraps an in-memory buffer and provides it with a Seek implementation.
Empty
Empty ignores any data written via Write, and will always be empty (returning zero bytes) when read via Read .
Error
The error type for I/O operations of the Read, Write, Seek, and associated traits.
IntoInnerError
An error returned by BufWriter::into_inner which combines an error that happened while writing out the buffer, and the buffered writer object which may be used to recover from the condition.
IoSlice
A buffer type used with Write::write_vectored.
IoSliceMut
A buffer type used with Read::read_vectored.
LineWriter
Wraps a writer and buffers output to it, flushing whenever a newline (0x0a, ‘\n’) is detected.

Lines An iterator over the lines of an instance of BufRead.

Repeat A reader which yields one byte over and over and over and over and over and…

Sink A writer which will move data into the void.

Split An iterator over the contents of an instance of BufRead split on a particular byte.

Stderr A handle to the standard error stream of a process.

StderrLock A locked reference to the Stderr handle.

Stdin A handle to the standard input stream of a process.

StdinLock A locked reference to the Stdin handle.

Stdout A handle to the global standard output stream of the current process.

StdoutLock A locked reference to the Stdout handle.

Take Reader adapter which limits the bytes read from an underlying reader.

WriterPanicked Error returned for the buffered data from BufWriter::into_parts, when the underlying writer has previously panicked. Contains the (possibly partly written) buffered data.

BorrowedBuf A borrowed byte buffer which is incrementally filled and initialized.

BorrowedCursor A writeable view of the unfilled portion of a BorrowedBuf.

Traits:

  • BufRead A BufRead is a type of Reader which has an internal buffer, allowing it to perform extra ways of reading.
  • IsTerminal Trait to determine if a descriptor/handle refers to a terminal/tty.
  • Read The Read trait allows for reading bytes from a source.
  • Seek The Seek trait provides a cursor which can be moved within a stream of bytes.
  • Write A trait for objects which are byte-oriented sinks.

Functions

  • copy : Copies the entire contents of a reader into a writer.
  • empty : Creates a value that is always at EOF for reads, and ignores all data written.
  • read_to_string : Read all bytes from a reader into a new String.
  • repeat:Creates an instance of a reader that infinitely repeats one byte.
  • sink: Creates an instance of a writer which will successfully consume all data.

如下三个函数返回系统标准的 stderr/stdin/stdout

  • stderr: Constructs a new handle to the standard error of the current process.
  • stdin: Constructs a new handle to the standard input of the current process.
  • stdout: Constructs a new handle to the standard output of the current process.

Type Aliases

  • Result: A specialized Result type for I/O operations. std::io 自定义的 Result 别名类型
  • RawOsErrorExperimental The type of raw OS error codes returned by Error::raw_os_error.

15.1 Read
#

必须实现的方法:最多读取 buf.len() 的数据,所以传入的 buf 类型是 slice,而非动态大小的 Vec/String

  • fn read(&mut self, buf: &mut [u8]) -> Result<usize>;

自动实现的方法:read_to_end/read_to_string() 都是读取全部内容,大小未知,所以传入Vec/String

  • fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { … }
  • fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { … }
  • bytes()
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> { ... }
    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> 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 { ... }
}

实现 Read 的类型:

impl Read for &File
impl Read for &TcpStream
impl Read for &[u8]  // &Vec<T> 和 &[T; N] 都实现了 Read,&str.as_bytes 也返回 &[u8]
impl Read for File
impl Read for TcpStream
impl Read for UnixStream

impl Read for ChildStderr // std::process 下的 ChildStderr/ChildStdout
impl Read for ChildStdout

impl Read for Arc<File>
impl Read for Empty
impl Read for Repeat
impl Read for Stdin
impl Read for StdinLock<'_>

impl<'a> Read for &'a UnixStream
impl<A: Allocator> Read for VecDeque<u8, A>
impl<R: Read + ?Sized> Read for &mut R
impl<R: Read + ?Sized> Read for Box<R>
impl<R: ?Sized + Read> Read for BufReader<R>
impl<T> Read for Cursor<T> where T: AsRef<[u8]>,
impl<T: Read> Read for Take<T>
impl<T: Read, U: Read> Read for Chain<T, U>

例子:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10]; // 指定 buf 大小为 10 bytes

    // read up to 10 bytes
    f.read(&mut buffer)?;

    let mut buffer = Vec::new();
    // read the whole file
    f.read_to_end(&mut buffer)?;

    // read into a String, so that you don't need to do the conversion.
    let mut buffer = String::new();
    f.read_to_string(&mut buffer)?;

    // and more! See the other methods for more details.
    Ok(())
}

15.2 BufRead
#

BufRead 是内部包含一个 buffer 的 Reader,它是 Read 的子类型,它提供了几个好用的方法:

  1. read_line(): 读取一行(包含行尾的换行)存入传入的 String buf;
  2. split(self): 返回一个迭代器,每次迭代 split 后的内容;
  3. lines(self): 返回一个迭代器,迭代返回 io::Result<String> ,字符串末尾不包含换行;
pub trait BufRead: Read {
    // Required methods
    fn fill_buf(&mut self) -> Result<&[u8]>;
    fn consume(&mut self, amt: usize);

    // Provided methods
    fn has_data_left(&mut self) -> Result<bool> { ... }
    fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> { ... }
    fn skip_until(&mut self, byte: u8) -> Result<usize> { ... }
    fn read_line(&mut self, buf: &mut String) -> Result<usize> { ... }
    fn split(self, byte: u8) -> Split<Self> where Self: Sized { ... }
    fn lines(self) -> Lines<Self> where Self: Sized { ... }
}

实现了 BufRead 的类型:&[u8]/StdinLock/BufReader/Cursor:

impl BufRead for &[u8]
impl BufRead for Empty
impl BufRead for StdinLock<'_>

impl<A: Allocator> BufRead for VecDeque<u8, A>
impl<B: BufRead + ?Sized> BufRead for &mut B
impl<B: BufRead + ?Sized> BufRead for Box<B>

impl<R: ?Sized + Read> BufRead for BufReader<R>

impl<T> BufRead for Cursor<T> where T: AsRef<[u8]>
impl<T: BufRead> BufRead for Take<T>
impl<T: BufRead, U: BufRead> BufRead for Chain<T, U>

15.3 Write
#

Write 是面向 byte 的输出:

  • write(): 将 buf 内容写入到 object,返回写出的字节数(所以一次 write 不一定写出了所有数据);
  • write_all():将 buf 内容写入到 object,确保全部都写入,否则返回 Err;
  • write_fmt(): 将传入的 Arguments(由 fmt!() 宏来生成)写入 object;
  • flush() : 确保 object 中 buff(如果有) 的数据写入到 true sink 中;
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
}

实现 Write 的类型:

impl Write for &File
impl Write for &TcpStream
impl Write for &ChildStdin
impl Write for &Empty
impl Write for &Sink
impl Write for &Stderr
impl Write for &Stdout
impl Write for &mut [u8]

impl Write for File
impl Write for TcpStream
impl Write for UnixStream
impl Write for ChildStdin
impl Write for Arc<File>
impl Write for Cursor<&mut [u8]>

impl Write for Empty
impl Write for Sink
impl Write for Stderr
impl Write for StderrLock<'_>
impl Write for Stdout
impl Write for StdoutLock<'_>

impl<'a> Write for &'a UnixStream
impl<'a> Write for BorrowedCursor<'a>
impl<A> Write for Cursor<&mut Vec<u8, A>> where A: Allocator,
impl<A> Write for Cursor<Box<[u8], A>> where A: Allocator,
impl<A> Write for Cursor<Vec<u8, A>> where A: Allocator,
impl<A: Allocator> Write for VecDeque<u8, A>
impl<A: Allocator> Write for Vec<u8, A>
impl<W: Write + ?Sized> Write for &mut W
impl<W: Write + ?Sized> Write for Box<W>
impl<W: ?Sized + Write> Write for BufWriter<W>
impl<W: ?Sized + Write> Write for LineWriter<W>
impl<const N: usize> Write for Cursor<[u8; N]>

示例:

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(())
}

use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = File::create("foo.txt")?;

    // this call
    write!(buffer, "{:.*}", 2, 1.234567)?;
    // turns into this:
    buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?;
    Ok(())
}

15.4 Cursor
#

pub struct Cursor<T> { /* private fields */ }

A Cursor wraps an in-memory buffer and provides it with a Seek implementation.

Cursors are used with in-memory buffers, anything implementing AsRef<[u8]>, to allow them to implement Read and/or Write, allowing these buffers to be used anywhere you might use a reader or writer that does actual I/O.

The standard library implements some I/O traits on various types which are commonly used as a buffer, like Cursor<Vec<u8>> and Cursor<&[u8]>.

Cursor 实现了 BufRead/Read/Seek/Write trait:

impl<T> BufRead for Cursor<T> where T: AsRef<[u8]>
impl<T> Read for Cursor<T> where T: AsRef<[u8]>
impl<T> Seek for Cursor<T> where T: AsRef<[u8]>

impl Write for Cursor<&mut [u8]>
impl<A> Write for Cursor<&mut Vec<u8, A>> where A: Allocator,
impl<const N: usize> Write for Cursor<[u8; N]>
impl<A> Write for Cursor<Box<[u8], A>> where A: Allocator,
impl<A> Write for Cursor<Vec<u8, A>> where A: Allocator,

例子:

use std::io::prelude::*;
use std::io::{self, SeekFrom};
use std::fs::File;

// a library function we've written
fn write_ten_bytes_at_end<W: Write + Seek>(mut writer: W) -> io::Result<()> {
    writer.seek(SeekFrom::End(-10))?;

    for i in 0..10 {
        writer.write(&[i])?;
    }

    // all went well
    Ok(())
}

// Here's some code that uses this library function.
//
// We might want to use a BufReader here for efficiency, but let's keep this example focused.
let mut file = File::create("foo.txt")?;

write_ten_bytes_at_end(&mut file)?;

// now let's write a test
#[test]
fn test_writes_bytes() {
    // setting up a real File is much slower than an in-memory buffer, let's use a cursor instead
    use std::io::Cursor;
    let mut buff = Cursor::new(vec![0; 15]); // 从 Vec<i32> 创建 Cursor

    write_ten_bytes_at_end(&mut buff).unwrap(); // Cursor 实现了 Seek/Write

    assert_eq!(&buff.get_ref()[5..15], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}

15.5 Empty
#

使用 std::io:empty() 函数返回 Empty 对象。Empty 类型实现了 BufRead/Read/Write/Seek trait:

  • 读返回 EOF;
  • 写被丢弃;
use std::io::{self, Write};
let buffer = vec![1, 2, 3, 5, 8];
let num_bytes = io::empty().write(&buffer).unwrap();
assert_eq!(num_bytes, 5);

use std::io::{self, Read};
let mut buffer = String::new();
io::empty().read_to_string(&mut buffer).unwrap();
assert!(buffer.is_empty());

15.6 Repeat
#

调用 std::io::repeat(byte: u8) 函数产生一个 Repeat 对象, 它实现了 Read trait,一直返回该 byte:

use std::io::{self, Read};

let mut buffer = [0; 3];
io::repeat(0b101).read_exact(&mut buffer).unwrap();
assert_eq!(buffer, [0b101, 0b101, 0b101]);

15.7 Stdin/StdinLock/Stdout/StdoutLock/Stderr
#

std::io::stdin()/stdout()/stderr() 分别返回上面三种类型:

stdin() -> Stdin: Each handle returned is a reference to a shared global buffer whose access is synchronized via a mutex. If you need more explicit control over locking, see the Stdin::lock method.

  • Stdin 的方法自动获得 mutex lock 进行同步;
  • 也可以调用 Stdin::lock() 来获得 StdinLock 对象, 然后调用它的 read_line 方法或者它实现的 BufRead/Read trait 方法;
// Using implicit synchronization:
use std::io;
fn main() -> io::Result<()> {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    Ok(())
}

// Using explicit synchronization:
use std::io::{self, BufRead};
fn main() -> io::Result<()> {
    let mut buffer = String::new();
    let stdin = io::stdin();
    let mut handle = stdin.lock();
    handle.read_line(&mut buffer)?;
    Ok(())
}

// pub fn lines(self) -> Lines<StdinLock<'static>>
use std::io;
let lines = io::stdin().lines();
for line in lines {
    println!("got a line: {}", line.unwrap());
}

pub fn stdout() -> Stdout :Constructs a new handle to the standard output of the current process.

Each handle returned is a reference to a shared global buffer whose access is synchronized via a mutex. If you need more explicit control over locking, see the Stdout::lock method.

Stdout/StdoutLock 实现了 write trait, 其中 Stdout 的方法自动 mutex 同步, StdoutLock 不自动同步:

// Using implicit synchronization:
use std::io::{self, Write};
fn main() -> io::Result<()> {
    io::stdout().write_all(b"hello world")?;
    Ok(())
}

// Using explicit synchronization:
use std::io::{self, Write};
fn main() -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    handle.write_all(b"hello world")?;
    Ok(())
}

pub fn stderr() -> Stderr : Constructs a new handle to the standard error of the current process. This handle is not buffered .

// Using implicit synchronization:
use std::io::{self, Write};
fn main() -> io::Result<()> {
    io::stderr().write_all(b"hello world")?;

    Ok(())
}

// Using explicit synchronization:
use std::io::{self, Write};
fn main() -> io::Result<()> {
    let stderr = io::stderr();
    let mut handle = stderr.lock();

    handle.write_all(b"hello world")?;

    Ok(())
}
pub fn sink() -> Sink

Creates an instance of a writer which will successfully consume all data.

Sink 将丢弃写入的数据.

All calls to write on the returned instance will return Ok(buf.len()) and the contents of the buffer will not be inspected.

Examples

use std::io::{self, Write};

let buffer = vec![1, 2, 3, 5, 8];
let num_bytes = io::sink().write(&buffer).unwrap();
assert_eq!(num_bytes, 5);

16 std::env
#

std::env module 提供了一些管理进程参数、环境变量、工作目录和临时目录的函数:

  1. args(): 获取进程命令行参数
  2. vars(): 获取进程环境变量列表
  3. var(): 获取进程特定环境变量
  4. remove_var/set_var(): 删除和设置环境变量
  5. temp_dir(): 返回临时目录
  6. current_dir/current_exec: 当前工作目录和二进制路径
  7. set_current_dir(): 设置进程当前工作目录;
  8. join_paths/split_paths(): 对 PATH 环境变量的路径进行处理;
use std::env;
use std::path::PathBuf;

fn main() -> Result<(), env::JoinPathsError> {
    if let Some(path) = env::var_os("PATH") {
        let mut paths = env::split_paths(&path).collect::<Vec<_>>();
        paths.push(PathBuf::from("/home/xyz/bin"));
        let new_path = env::join_paths(paths)?;
        env::set_var("PATH", &new_path);
    }
    Ok(())
}

Functions

args
Returns the arguments that this program was started with (normally passed via the command line).
args_os
Returns the arguments that this program was started with (normally passed via the command line).
current_dir
Returns the current working directory as a PathBuf.
current_exe
Returns the full filesystem path of the current running executable.
home_dir
Returns the path of the current user’s home directory if known.
join_paths
Joins a collection of Paths appropriately for the PATH environment variable.
remove_var
Removes an environment variable from the environment of the currently running process.
set_current_dir
Changes the current working directory to the specified path.
set_var
Sets the environment variable key to the value value for the currently running process.
split_paths
Parses input according to platform conventions for the PATH environment variable.
temp_dir
Returns the path of a temporary directory.
var
Fetches the environment variable key from the current process.
var_os
Fetches the environment variable key from the current process, returning None if the variable isn’t set or if there is another error.
vars
Returns an iterator of (variable, value) pairs of strings, for all the environment variables of the current process.
vars_os
Returns an iterator of (variable, value) pairs of OS strings, for all the environment variables of the current process.

pub fn args() -> Args, Args 是可迭代对象, 返回 String:

use std::env;
for argument in env::args() {
    println!("{argument}");
}

var/set_var/remove_var() 读取、设置和删除环境变量:

use std::env;

let key = "KEY";
env::set_var(key, "VALUE");
assert_eq!(env::var(key), Ok("VALUE".to_string()));

env::remove_var(key);
assert!(env::var(key).is_err());

match env::var(key) {
    Ok(val) => println!("{key}: {val:?}"),
    Err(e) => println!("couldn't interpret {key}: {e}"),
}

17 std::path
#

Cross-platform path manipulation.

This module provides two types, PathBuf and Path (akin to String and str), for working with paths abstractly. These types are thin wrappers around OsString and OsStr respectively, meaning that they work directly on strings according to the local platform’s path syntax.

Paths can be parsed into Components by iterating over the structure returned by the components method on Path. Components roughly correspond to the substrings between path separators (/ or \). You can reconstruct an equivalent path from components with the push method on PathBuf; note that the paths may differ syntactically by the normalization described in the documentation for the components method.

Structs

  • Ancestors An iterator over Path and its ancestors.
  • Components An iterator over the Components of a Path.
  • Iter An iterator over the Components of a Path, as OsStr slices.
  • Display Helper struct for safely printing paths with format! and {}.
  • Path A slice of a path (akin to str).
  • PathBuf An owned, mutable path (akin to String).
  • PrefixComponent A structure wrapping a Windows path prefix as well as its unparsed string representation.
  • StripPrefixError An error returned from Path::strip_prefix if the prefix was not found.

Path/PathBuf 用于实现 OS 无关的路径字符串。 Path 是 unsized type,所以一般需要和 & 或 Box 使用。不可以改变,类似于 str。PathBuf 也是OS 无关的路径字符串,是 owned 版本,可以改变,类似于 String。

Struct std::path::Path

pub struct Path { /* private fields */ }

// 示例
use std::path::Path;
use std::ffi::OsStr;

// Note: this example does work on Windows
let path = Path::new("./foo/bar.txt"); // 传入的类型需要实现 AsRef<OsStr>
let string = String::from("foo.txt"); // &str,&String,&Path 都实现了 AsRef<OsStr>
let from_string = Path::new(&string);
let from_path = Path::new(&from_string);
assert_eq!(from_string, from_path);

let parent = path.parent();
assert_eq!(parent, Some(Path::new("./foo")));

let file_stem = path.file_stem();
assert_eq!(file_stem, Some(OsStr::new("bar")));

let extension = path.extension();
assert_eq!(extension, Some(OsStr::new("txt")));

Path 的方法:

  • pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path
  • pub fn as_os_str(&self) -> &OsStr
  • pub fn as_mut_os_str(&mut self) -> &mut OsStr
  • pub fn to_str(&self) -> Option<&str>
  • pub fn to_path_buf(&self) -> PathBuf // 转换为 PathBuf
  • pub fn parent(&self) -> Option<&Path>
  • pub fn file_name(&self) -> Option<&OsStr>
  • pub fn strip_prefix<P>(&self, base: P) -> Result<&Path, StripPrefixError>
  • pub fn extension(&self) -> Option<&OsStr>
  • pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf // Path 不可变,返回可变的 PathBuf
  • pub fn with_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf

Path 和文件系统相关方法:

  • pub fn read_link(&self) -> Result<PathBuf>
  • pub fn read_dir(&self) -> Result<ReadDir> // ReadDir 是一迭代器, 返回 io::Result<fs::DirEntry>
  • pub fn exists(&self) -> bool
  • pub fn is_file(&self) -> bool
  • pub fn is_dir(&self) -> bool
  • pub fn is_symlink(&self) -> bool
  • pub fn metadata(&self) -> Result<Metadata> // 返回的 Metadata 是 fs::metadata 的别名类型
  • pub fn symlink_metadata(&self) -> Result<Metadata>

&str/&String/&Path/&PathBuf/&OsStr/&OsString 均实现了 AsRef<OsStr>,都可以作为 Path::new() 的参数:

impl AsRef<OsStr> for Component<'_>
impl AsRef<OsStr> for str
impl AsRef<OsStr> for OsStr
impl AsRef<OsStr> for OsString
impl AsRef<OsStr> for Components<'_>
impl AsRef<OsStr> for std::path::Iter<'_>
impl AsRef<OsStr> for Path
impl AsRef<OsStr> for PathBuf
impl AsRef<OsStr> for String

impl AsRef<Path> for Cow<'_, OsStr>
impl AsRef<Path> for Component<'_>
impl AsRef<Path> for str
impl AsRef<Path> for OsStr
impl AsRef<Path> for OsString
impl AsRef<Path> for Components<'_>
impl AsRef<Path> for std::path::Iter<'_>
impl AsRef<Path> for Path
impl AsRef<Path> for PathBuf
impl AsRef<Path> for String

Struct std::path::PathBuf

// An owned, mutable path (akin to String).
pub struct PathBuf { /* private fields */ }

use std::path::PathBuf;
let mut path = PathBuf::new();
path.push(r"C:\");
path.push("windows");
path.push("system32");
path.set_extension("dll");

PathBuf 的方法:

  • pub fn new() -> PathBuf
  • pub fn with_capacity(capacity: usize) -> PathBuf
  • pub fn as_path(&self) -> &Path // 转换为 Path
  • pub fn push<P: AsRef<Path>>(&mut self, path: P)
  • pub fn set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S)
  • pub fn set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool

PathBuf 实现了 Deref<Target=Path>,所以 PathBuf 可以使用 Path 的所有方法,并且 &PathBuf 可以当作 &Path 使用。

18 std::fs
#

文件和目录操作: std::fs 下泛型函数如果输入是 Path, 则是 AsRef<Path>, 所以实现了该 trait 的对象均可。

impl AsRef<Path> for Cow<'_, OsStr>
impl AsRef<Path> for Component<'_>
impl AsRef<Path> for str
impl AsRef<Path> for OsStr
impl AsRef<Path> for OsString
impl AsRef<Path> for Components<'_>
impl AsRef<Path> for std::path::Iter<'_>
impl AsRef<Path> for Path
impl AsRef<Path> for PathBuf
impl AsRef<Path> for String

Structs

  • DirBuilder A builder used to create directories in various manners.
  • DirEntry Entries returned by the ReadDir iterator.
  • File An object providing access to an open file on the filesystem.
  • FileTimes Representation of the various timestamps on a file.
  • FileType A structure representing a type of file with accessors for each file type. It is returned by Metadata::file_type method.
  • Metadata Metadata information about a file.
  • OpenOptions Options and flags which can be used to configure how a file is opened.
  • Permissions Representation of the various permissions on a file.
  • ReadDir Iterator over the entries in a directory.

Functions

  • canonicalize : Returns the canonical, absolute form of a path with all intermediate components normalized and symbolic links resolved.
  • copy : Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file.
  • create_dir : Creates a new, empty directory at the provided path
  • create_dir_all : Recursively create a directory and all of its parent components if they are missing.
  • hard_link : Creates a new hard link on the filesystem.
  • metadata : Given a path, query the file system to get information about a file, directory, etc.
  • read : Read the entire contents of a file into a bytes vector.
  • read_dir : Returns an iterator over the entries within a directory.
  • read_link : Reads a symbolic link, returning the file that the link points to.
  • read_to_string : Read the entire contents of a file into a string.
  • remove_dir : Removes an empty directory.
  • remove_dir_all : Removes a directory at this path, after removing all its contents. Use carefully!
  • remove_file : Removes a file from the filesystem.
  • rename : Rename a file or directory to a new name, replacing the original file if to already exists.
  • set_permissions : Changes the permissions found on a file or a directory.
  • soft_linkDeprecated : Creates a new symbolic link on the filesystem.
  • symlink_metadata Query the metadata about a file without following symlinks.
  • write Write a slice as the entire contents of a file.
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
    let data: Vec<u8> = fs::read("image.jpg")?;
    assert_eq!(data[0..3], [0xFF, 0xD8, 0xFF]);
    Ok(())

    let message: String = fs::read_to_string("message.txt")?;
    println!("{}", message);
    Ok(())
}

use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
#[cfg(target_family = "unix")]
use std::os::unix;
#[cfg(target_family = "windows")]
use std::os::windows;
use std::path::Path;

// A simple implementation of `% cat path`
fn cat(path: &Path) -> io::Result<String> {
    let mut f = File::open(path)?;
    let mut s = String::new();
    match f.read_to_string(&mut s) { // 将文件的内容读取到字符串中
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

// A simple implementation of `% echo s > path`
fn echo(s: &str, path: &Path) -> io::Result<()> {
    let mut f = File::create(path)?;
    f.write_all(s.as_bytes()) // 将 &[u8] 的内容一次性写入文件
}

// A simple implementation of `% touch path` (ignores existing files)
fn touch(path: &Path) -> io::Result<()> {
    match OpenOptions::new().create(true).write(true).open(path) {
        Ok(_) => Ok(()),
        Err(e) => Err(e),
    }
}

fn main() {
    println!("`mkdir a`");
    // Create a directory, returns `io::Result<()>`
    match fs::create_dir("a") {
        Err(why) => println!("! {:?}", why.kind()),
        Ok(_) => {},
    }

    println!("`echo hello > a/b.txt`");
    // The previous match can be simplified using the `unwrap_or_else` method
    echo("hello", &Path::new("a/b.txt")).unwrap_or_else(|why| {
        println!("! {:?}", why.kind());
    });

    println!("`mkdir -p a/c/d`");
    // Recursively create a directory, returns `io::Result<()>`
    fs::create_dir_all("a/c/d").unwrap_or_else(|why| {
        println!("! {:?}", why.kind());
    });

    println!("`touch a/c/e.txt`");
    touch(&Path::new("a/c/e.txt")).unwrap_or_else(|why| {
        println!("! {:?}", why.kind());
    });

    println!("`ln -s ../b.txt a/c/b.txt`");
    // Create a symbolic link, returns `io::Result<()>`
    #[cfg(target_family = "unix")] {
        unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
            println!("! {:?}", why.kind());
        });
    }
    #[cfg(target_family = "windows")] {
        windows::fs::symlink_file("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
            println!("! {:?}", why.to_string());
        });
    }

    println!("`cat a/c/b.txt`");
    match cat(&Path::new("a/c/b.txt")) {
        Err(why) => println!("! {:?}", why.kind()),
        Ok(s) => println!("> {}", s),
    }

    println!("`ls a`");
    // Read the contents of a directory, returns `io::Result<Vec<Path>>`
    match fs::read_dir("a") {
        Err(why) => println!("! {:?}", why.kind()),
        Ok(paths) => for path in paths {
            println!("> {:?}", path.unwrap().path());
        },
    }

    println!("`rm a/c/e.txt`");
    // Remove a file, returns `io::Result<()>`
    fs::remove_file("a/c/e.txt").unwrap_or_else(|why| {
        println!("! {:?}", why.kind());
    });

    println!("`rmdir a/c/d`");
    // Remove an empty directory, returns `io::Result<()>`
    fs::remove_dir("a/c/d").unwrap_or_else(|why| {
        println!("! {:?}", why.kind());
    });
}

19 std::time
#

提供了如下类型:

Duration
代表时间间隔;
Instant
代表某一时刻, 可用于计算执行耗时;(一般用于记录进程时间)
SystemTime
代表某一个时刻, 和 Instant 不同的是,它不能确保是单调递增的(一般用于保存文件时间)。
let five_seconds = Duration::from_secs(5);
assert_eq!(five_seconds, Duration::from_millis(5_000));
assert_eq!(five_seconds, Duration::from_micros(5_000_000));
assert_eq!(five_seconds, Duration::from_nanos(5_000_000_000));

let ten_seconds = Duration::from_secs(10);
let seven_nanos = Duration::from_nanos(7);
let total = ten_seconds + seven_nanos; // Duration 实现了算术运算符重载
assert_eq!(total, Duration::new(10, 7)); // new() 参数是秒和纳秒

let now = Instant::now();
slow_function();
let elapsed_time = now.elapsed();
println!("Running slow_function() took {} seconds.", elapsed_time.as_secs());

Duration 代表时间跨度,由 second 和 nanoseconds 组成。实现了 Default/Add/Sub 等 std::ops 中运算符重载 trait。 Duration 默认实现了 Debug 而非 Display

use std::time::Duration;

let five_seconds = Duration::new(5, 0); // new() 参数是秒和纳秒
let five_seconds_and_five_nanos = five_seconds + Duration::new(0, 5);

assert_eq!(five_seconds_and_five_nanos.as_secs(), 5);
assert_eq!(five_seconds_and_five_nanos.subsec_nanos(), 5); // 只返回小数部分,单位是 nanos

let ten_millis = Duration::from_millis(10);

Duration 的方法或函数:

  • 实现了 Add/AddAssign/Sub/SubAssign/Div/DivAssign 等运算符,可以直接进行算术运算。
  • Instant.elapsed() 返回 Duration,而且方法参数也是 Duration;
// 创建
pub const fn new(secs: u64, nanos: u32) -> Duration
pub const fn from_secs(secs: u64) -> Duration
pub const fn from_millis(millis: u64) -> Duration
pub const fn from_micros(micros: u64) -> Duration
pub const fn from_nanos(nanos: u64) -> Duration

pub const fn is_zero(&self) -> bool

// 返回
pub const fn as_secs(&self) -> u64
pub const fn as_millis(&self) -> u128
pub const fn as_micros(&self) -> u128
pub const fn as_nanos(&self) -> u128

// 返回不足一秒的小数部分
pub const fn subsec_millis(&self) -> u32
pub const fn subsec_micros(&self) -> u32
pub const fn subsec_nanos(&self) -> u32

pub const fn abs_diff(self, other: Duration) -> Duration

pub const fn checked_add(self, rhs: Duration) -> Option<Duration>
pub const fn saturating_add(self, rhs: Duration) -> Duration
pub const fn checked_sub(self, rhs: Duration) -> Option<Duration>
pub const fn saturating_sub(self, rhs: Duration) -> Duration
pub const fn checked_mul(self, rhs: u32) -> Option<Duration>
pub const fn saturating_mul(self, rhs: u32) -> Duration
pub const fn checked_div(self, rhs: u32) -> Option<Duration>

pub fn as_secs_f64(&self) -> f64
pub fn as_secs_f32(&self) -> f32
pub fn from_secs_f64(secs: f64) -> Duration
pub fn from_secs_f32(secs: f32) -> Duration
pub fn mul_f64(self, rhs: f64) -> Duration
pub fn mul_f32(self, rhs: f32) -> Duration
pub fn div_f64(self, rhs: f64) -> Duration
pub fn div_f32(self, rhs: f32) -> Duration
pub fn div_duration_f64(self, rhs: Duration) -> f64
pub fn div_duration_f32(self, rhs: Duration) -> f32

impl Duration
pub fn try_from_secs_f32(secs: f32) -> Result<Duration, TryFromFloatSecsError>
pub fn try_from_secs_f64(secs: f64) -> Result<Duration, TryFromFloatSecsError>

Instant 代表某一个时刻,在程序中是单调递增的,故可以用来测量代码执行时间。

use std::time::{Duration, Instant};
use std::thread::sleep;

fn main() {
   let now = Instant::now();
   // we sleep for 2 seconds
   sleep(Duration::new(2, 0));
   // it prints '2'
   println!("{}", now.elapsed().as_secs());
}

Instant 的方法或函数:

impl Instant
pub fn now() -> Instant

// 两个 Instant 之间的差距
pub fn duration_since(&self, earlier: Instant) -> Duration
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration>
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration

// 时间流逝
pub fn elapsed(&self) -> Duration

// 添加 Duration 后的新 Instant
pub fn checked_add(&self, duration: Duration) -> Option<Instant>
pub fn checked_sub(&self, duration: Duration) -> Option<Instant>

20 std::sync
#

20.1 mpsc
#

let (tx, rx) = mpsc::channel();

  1. tx 支持 clone() ,从而可以在多个线程中使用, 而 rx 不支持 clone(),所以只能有一个实例。如果要在多个线程中并发访问 rx,则需要 Arc + Mutext;
  2. tx clone 后的多个对象必须都被 drop 后, rx.recv() 才不会继续被 blocking;
  3. tx 发送的所有数据都串行缓冲, 即使 tx 都被 drop, 内容还在, 直到 rx 接受完数据;
  4. 数据发往 tx 后,所有权被转移;
let (tx, rx) = mpsc::channel();

let tx1 = tx.clone();
thread::spawn(move || {
    let vals = vec![
        String::from("hi"),
        String::from("from"),
        String::from("the"),
        String::from("thread"),
    ];

    for val in vals {
        tx1.send(val).unwrap(); // val 所有权被转移到 channel
        thread::sleep(Duration::from_secs(1));
    }
});

thread::spawn(move || {
    let vals = vec![
        String::from("more"),
        String::from("messages"),
        String::from("for"),
        String::from("you"),
    ];

    for val in vals {
        tx.send(val).unwrap();
        thread::sleep(Duration::from_secs(1));
    }
});

for received in rx { // 所有权转移给接收者。
    println!("Got: {}", received);
}

20.2 mutex
#

Mutex 可以作为全局 static 对象,用 Arc 包裹后可以在多线程环境中使用。

由于 MutexGuard 实现了 DerefMut<Target=T>, 所以可以像 &mut T 一样使用 data 变量.

  • Mutex 支持内部可变性:mutex.lock().unwrap() 返回的是一个新 MutexGuard<’_, T> 对象, 所以可以给它赋值

给 mut 类型变量(let mut data = data.lock().unwrap()), 进而可以对 MutexGuard 进行修改。

use std::sync::{Mutex, Arc};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap()); // 这里 counter 前的 * 是可选的。
}

20.3 condvar
#

Condvar 需要和 Mutex 一块使用.

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);

thread::spawn(move|| {
    let (lock, cvar) = &*pair2;  // deref
    let mut started = lock.lock().unwrap(); // started 是 MutexGuard 类型, 被 drop 时自动释放锁
    *started = true;
    cvar.notify_one();
});

// Wait for the thread to start up.
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap(); // started 是 MutexGuard 类型
while !*started { // MutxtGuard 实现了 Deref, 所以 *started 返回内部的 bool 值
    // wait 需要传入 MutexGuard, 执行 wait() 时内部会使用 MutexGuard 释放锁, 然后在收到 notify 时会再次获得锁
    started = cvar.wait(started).unwrap();
}

20.4 atomic
#

各种 atomic 类型如 AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16 支持跨线程的原子更新, 主要实现无锁并发:

  • 各方法, 如 load/store/swap/fetch 等, 都是 &self, 不需要 &mut self;
  • 各 atomic 类型实现了 Sync 但没有实现 Send, 需要包裹在 Arc 中使用.
  • rust atomic follow C++20 atomics 规范, 每个方法都接受一个 Ordering 参数来指定 memory barrier 类型.
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{hint, thread};

fn main() {
    let spinlock = Arc::new(AtomicUsize::new(1));

    let spinlock_clone = Arc::clone(&spinlock);
    let thread = thread::spawn(move|| {
        spinlock_clone.store(0, Ordering::SeqCst);
    });

    // Wait for the other thread to release the lock
    while spinlock.load(Ordering::SeqCst) != 0 {
        hint::spin_loop();
    }

    if let Err(panic) = thread.join() {
        println!("Thread had an error: {panic:?}");
    }
}

另外, atomic 支持内部可变性, 所以可以定义为全局变量, 后续可以原子修改. 也可以用 Arc 包裹后在多线程环境中使用。

// Keep a global count of live threads
use std::sync::atomic::{AtomicUsize, Ordering};

// 全局 static 变量, 即使不是 static mut 后续也可以更新(因为 atomic 的 fetch/load 的参数都是 &self
static GLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);
let old_thread_count = GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
println!("live threads: {}", old_thread_count + 1);

通用方法:

pub fn compare_exchange(
    &self,
    current: bool,
    new: bool,
    success: Ordering,
    failure: Ordering,
) -> Result<bool, bool> {

pub fn compare_exchange_weak(
    &self,
    current: bool,
    new: bool,
    success: Ordering,
    failure: Ordering
) -> Result<bool, bool>

pub fn load(&self, order: Ordering) -> bool
pub fn store(&self, val: bool, order: Ordering)
pub fn swap(&self, val: bool, order: Ordering) -> bool

Ordering 类型:

pub enum Ordering {
    Relaxed,
    Release,
    Acquire,
    AcqRel,
    SeqCst,
}
  • Relaxed: 最宽松;
  • Release: store 写入操作时指定;
  • Acquire: load 读取时指定;
  • AcqRel: 同时 Acquire 和 Release, 在 load&store 时使用;
  • SeqCst: 最严格的模式;

20.5 Once/OnceLock/OnceCell
#

Once 是一个用于只进行一次全局初始化的类型,特别适用于 FFI 等场景:

  • 第一次调用 Once 类型值的 方法时,会执行对应闭包中的初始化逻辑,后续不会再执行该函数。
pub struct Once { /* private fields */ }

// 示例
use std::sync::Once;
static START: Once = Once::new();
START.call_once(|| {
    // run initialization here
});


// 另一个例子,用于设置全局变量 VAL
use std::sync::Once;
static mut VAL: usize = 0;
static INIT: Once = Once::new();

// Accessing a `static mut` is unsafe much of the time, but if we do so in a synchronized fashion
// (e.g., write once or read all) then we're good to go!
//
// This function will only call `expensive_computation` once, and will otherwise always return the
// value returned from the first invocation.
fn get_cached_val() -> usize {
    unsafe {
        INIT.call_once(|| {
            VAL = expensive_computation();
        });
        VAL
    }
}
fn expensive_computation() -> usize {
    // ...
}


// 初始化和查询
use std::sync::Once;
static INIT: Once = Once::new();
assert_eq!(INIT.is_completed(), false);
INIT.call_once(|| {
    assert_eq!(INIT.is_completed(), false);
});
assert_eq!(INIT.is_completed(), true);

Once 类型实现的方法:

impl Once
pub const fn new() -> Once

// Performs an initialization routine once and only once. The given closure will be executed if
// this is the first time call_once has been called, and otherwise the routine will not be
// invoked.
pub fn call_once<F>(&self, f: F) where F: FnOnce()

// Performs the same function as call_once() except ignores poisoning.
pub fn call_once_force<F>(&self, f: F) where F: FnOnce(&OnceState),

// Returns true if some call_once() call has completed successfully.
pub fn is_completed(&self) -> bool

Struct std::sync::OnceLock 是线程安全的 Struct std::cell::OnceCell 版本,用于封装 只能写入一次 的变量:

  • OnceLock 内部基于 Once 实现,只是 Once 使用 call_once () 初始化方法,而 OnceLock 提供了 get/get_or_init/set/take() 等更方便的方法。
  • OnceLock 实现了内部可变性 ,除了 get_mut() 方法是 &mut self 外,其他方法都是 &self 或 self, 而且 OnceLock 实现了 Send/Sync/Clone,所以可以 在多线程环境中无锁使用(不需要 move,也不需要加锁)

OnceLock 的方法:

  • 使用 set()/get_or_init()/get_or_try_init() 方法来设置一次 OnceLock。
  • static 常量具有 ‘static 生命周期,所以可以在多个线程里并发访问。(线程里不允许有非 ‘static 的引用,需要使用 move 闭包拥有所有权)
impl<T> OnceLock<T>
pub const fn new() -> OnceLock<T>

// Gets the reference to the underlying value.
// Returns None if the cell is empty, or being initialized. This method never blocks.
pub fn get(&self) -> Option<&T>

pub fn get_mut(&mut self) -> Option<&mut T>

// OnceLock 支持内部可变性,下面的方法都是 &self,所以可以同时在多个线程中无锁使用,也不需要 move。
pub fn set(&self, value: T) -> Result<(), T>
pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)>
pub fn get_or_init<F>(&self, f: F) -> &T where F: FnOnce() -> T
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E> where F: FnOnce() -> Result<T, E>

pub fn into_inner(self) -> Option<T>
pub fn take(&mut self) -> Option<T>

示例:

  1. 在函数内部定义 OnceLock 类型的 static 变量,实现 lazy static 或 memoizing ;
  2. 实现全局 static 常量;(因为只能写入或初始化一次,后续都是只读,所以不需要加锁。另外具有内部可变性,所以不需要是 static mut 静态变量。)
// 在函数内部定义 OnceLock 类型的 static 变量,实现 lazy static 或 memoizing
use std::sync::OnceLock;

struct DeepThought {
    answer: String,
}

impl DeepThought {
    fn new() -> Self {
        Self {
            // M3 Ultra takes about 16 million years in --release config
            answer: Self::great_question(),
        }
    }
}

fn computation() -> &'static DeepThought {
    // n.b. static items do not call [`Drop`] on program termination, so if
    // [`DeepThought`] impls Drop, that will not be used for this instance.

    // 静态常量(不是 static mut 静态变量),函数返回后还存在。
    static COMPUTATION: OnceLock<DeepThought> = OnceLock::new();
    // COMPUTATION 具有内部可变性,所以在共享借用情况下,调用 get_or_init() 方法,只会执行一次 init。
    COMPUTATION.get_or_init(|| DeepThought::new())
}

// The `DeepThought` is built, stored in the `OnceLock`, and returned.
let _ = computation().answer;

// The `DeepThought` is retrieved from the `OnceLock` and returned.
let _ = computation().answer;


// 另一个例子:在另一个线程中写入 OnceLock
use std::sync::OnceLock;

// 全局静态常量(具有内部可变性)
static CELL: OnceLock<usize> = OnceLock::new();

// `OnceLock` has not been written to yet.
assert!(CELL.get().is_none());

// Spawn a thread and write to `OnceLock`.
std::thread::spawn(|| {
    // 由于 CELL.get_or_init(&self, f: F) 是 &self,所以不需要 move
    let value = CELL.get_or_init(|| 12345);
    assert_eq!(value, &12345);
})
    .join()
    .unwrap();

// `OnceLock` now contains the value.
assert_eq!(
    CELL.get(),
    Some(&12345),
);

20.6 LazyLock/LazyCell
#

Rust 1.80 开始支持 LazyLock, 广泛使用的 lazy_static 可以使用 std::sync::LazyLock 来代替。

相比 OnceLock, 更建议使用 LazyLock<T, F>,因为 LazyLock 在首次 Deref<Target=T> 自动调用 F 闭包 进行初始化,而 OnceLock 需要显式的调用 get_or_init() 方法来进行初始化。

std::sync::LazyLockstd::cell::LazyCell 的线程安全版本,内部封装了 Once 和 UnsafeCell, 也具有内部可变性 ,所以可以用于定义 static 常量。

  • static 常量具有 ‘static 生命周期,所以可以在多个线程里并发访问。(线程里不允许有非 ‘static 的引用,需要使用 move 闭包获得所有权)
use std::sync::LazyLock;

// 创建 LazyLock 时指定初始化闭包。
// DEEP_THOUGHT 具有内部可变性,不需要定义为 static mut 类型。
static DEEP_THOUGHT: LazyLock<String> = LazyLock::new(|| {
    another_crate::great_question()
});
// 第一个 Deref 时自动调用初始化闭包。
let _ = &*DEEP_THOUGHT;
// 后续返回已初始化的值。
let _ = &*DEEP_THOUGHT;


// Lazyock 用在 struct field 中
use std::sync::LazyLock;

#[derive(Debug)]
struct UseCellLock {
    number: LazyLock<u32>,
}
fn main() {
    let lock: LazyLock<u32> = LazyLock::new(|| 0u32);

    let data = UseCellLock { number: lock }; // 此时 number field 未被初始化
    println!("{}", *data.number); // number field 被初始化
}

21 std::thread
#

并发编程(concurrent programming)与并行编程(parallel programming)这两种概念随着计算机设备的多核心化而变得越来越重要。前者允许程序中的不同部分相互独立地运行,而后者则允许程序中的不同部分同时执行。

由于绿色线程的 M:N 模型需要一个较大的运行时来管理线程,所以 Rust 标准库只提供了 1:1 线程模型的实现。

Rust thread 使用 thread::spawn() 来运行一个 thread:

pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static,  // 闭包函数必须满足 Send + 'static
    T: Send + 'static,

由于该闭包函数 F 在另一个 thread 中运行,thread closure 不能使用外围对象的共享引用 :因为编译器不能确定该 thread 和外围 thread 的生命周期关系,如果外围 thread 退出导致对象被 drop,则该 thread 的引用将失效。所以,一般使用 move 来将引用对象的所有权转移到闭包中, 从而满足 ‘static 定义。

  • 例外情况是,闭包中只引用 static 静态(全局)常量,由于全局常量具有 ‘static 生命周期,这时不需要 move 转移所有权。常见的使用场景是跨进程访问的,具有内部可变性的全局静态变量 static GLOBAL_VAR = Arc<Mutex<T>>;
// 错误
use std::thread;
fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v); // 编译失败,没有加 move 时默认是 reference
    });

    handle.join().unwrap();
}

// 正确
fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(move || { // move 指示 closure 捕获的外部对象,都是 ownership 接管
        // 而非引用的方式
        println!("Here's a vector: {:?}", v);
    });
    drop(v); // oh no! 错误,因为 v ownership 已经转义到线程闭包函数中,故这里不能再引用它。
    handle.join().unwrap();
}

通过在线程闭包函数中 move 接管外部对象,Rust 的 ownership 规则可以防止并发编程错误(编译时报错)。

如果只能有一个变量拥有对象所有权,当要在多个线程中共享同一个对象时,可以使用线程安全的 Arc<T> 智能指针,通过 clone 创建的 Arc<T> 都是 T 的共享引用,不能修改。

let value = Arc::new(myType);

for _ in 0..5 {
    // 每创建一个 thread 前,先 clone Arc,增加 value 的引用计数
    let value2 = Arc::clone(&value); // 或者 let value2 = value.clone();

    // move closure 函数捕获 value2 ownership
    thread.spawn(move || {
        let f = value2.filed;
        // ...
        // 线程推出时,value2 被 drop,引用计数减少
    })
}

由于 Mutext<T>/RWlock<T>/AutomicXX<T> 都是线程安全的 内部可变性对象 ,所以通过 Arc<Mutex<T>> Deref 生成的 mutex 共享引用,可以调用能修改它内部状态的 lock() 方法。

闭包的返回值可以通过 JoinHandler.join() 来获取, 它返回的是 std::thread::Result 类型, 如果子线程 panic 则对应 Err, 否则 Ok(value) ,value 值为 spawn() 闭包函数的返回值。

可以使用 builder 模式来自定义 thread name/stack_size 参数(默认是 2MiB,也可以通过 RUST_MIN_STACK 环境变量来设置); thread name 是可选的。

use std::thread;

let thread_join_handle = thread::spawn(move || {
    // some work here
});
// some work here
let res = thread_join_handle.join().unwrap();


// 配置 thread, 如 name/stack_size
use std::thread;
let handler = thread::Builder::new().name("thread1".to_string()).spawn(move || {
    println!("Hello, world!");
}).unwrap();
handler.join().unwrap();

Thread 对象的方法:id()/name()/unpark()

  • id() 返回的 std::thread::ThreadId 可以通过 as_u64() 返回他的编号;
  • 每个线程都有一个无锁的 blocking 机制,使用 std::thread::park()/park_timeout() 来暂停,然后使用 Thread 对象的 unpark()方法来继续。如果在线程运行前,已经被 unpark(), 则线程运行到 thread::park() 时会直接返回。
use std::thread;
use std::time::Duration;

let parked_thread = thread::Builder::new()
    .spawn(|| {
        println!("Parking thread");
        thread::park(); // 暂停
        println!("Thread unparked");
    })
    .unwrap();

// Let some time pass for the thread to be spawned.
thread::sleep(Duration::from_millis(10));

println!("Unpark the thread");
parked_thread.thread().unpark(); // 继续
parked_thread.join().unwrap();


// 另一例子,使用 park/unpark 来进行初始化同步
use std::thread;
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
use std::time::Duration;

let flag = Arc::new(AtomicBool::new(false));
let flag2 = Arc::clone(&flag);

let parked_thread = thread::spawn(move || {
    // We want to wait until the flag is set. We *could* just spin, but using
    // park/unpark is more efficient.
    while !flag2.load(Ordering::Relaxed) {
        println!("Parking thread");
        thread::park();
        // We *could* get here spuriously, i.e., way before the 10ms below are over!
        // But that is no problem, we are in a loop until the flag is set anyway.
        println!("Thread unparked");
    }
    println!("Flag received");
});

// Let some time pass for the thread to be spawned.
thread::sleep(Duration::from_millis(10));

// Set the flag, and let the thread wake up.
// There is no race condition here, if `unpark`
// happens first, `park` will return immediately.
// Hence there is no risk of a deadlock.
flag.store(true, Ordering::Relaxed);
println!("Unpark the thread");
parked_thread.thread().unpark();

parked_thread.join().unwrap();

21.1 scope thread
#

thread closure 的签名是 dyn FnOnce() -> T + Send + 'static ,thread 因为不能确保在主函数返回前 thread 一定执行完毕和被 join, 所以不能使用父线程 stack 上的变量引用(不满足 ‘static 要求):

use std::thread;

let people = vec![
    "Alice".to_string(),
    "Bob".to_string(),
    "Carol".to_string(),
];

let mut threads = Vec::new();

for person in &people {
    threads.push(thread::spawn(move || {
        println!("Hello, {}!", person);
    }));
}

for thread in threads {
    thread.join().unwrap();
}

报错:

error[E0597]: `people` does not live long enough
  --> src/main.rs:12:20
   |
12 |     for person in &people {
   |                    ^^^^^^ borrowed value does not live long enough
...
21 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

解决办法: 使用 scope thread , 它确保在 scope 函数返回前, 内部 spawn 的 thread 都被 join(显式或自动), 所以可以不使用 move 的情况下引用 stack 变量:

  • std::thread::scope() 的参数是一个传入 Scope 类型的闭包,使用 scope.spawn() 来创建线程;
  • scope() 函数返回前自动 join 所有 spawn 的线程;
  • Scope.spawn() 的参数是线程闭包函数 FnOnce(), 内部可以共享引用 stack 变量, 也可以 mut 引用 stack 变量, 但是要确保只能有一个 spawn() 闭包是 mut 使用该变量;
pub fn scope<'env, F, T>(f: F) -> T
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,  // 没有 Send + 'static 的要求

// 示例
use std::thread;

let mut a = vec![1, 2, 3];
let mut x = 0;

thread::scope(|s| { // s 是一个 &std::thread::Scope 类型对象
    s.spawn(|| {  // 使用 Scope.spawn() 来创建闭包
        println!("hello from the first scoped thread");
        dbg!(&a); // 共享借用 a
    });
    s.spawn(|| {
        println!("hello from the second scoped thread");
        x += a[0] + a[2]; // 共享借用 a ,可变借用 x
    });
    println!("hello from the main thread");
}); // thread::scope() 确保内部 spawn 的线程都执行返回后,再返回。

a.push(4);
assert_eq!(x, a.len());

let mut data = vec![5];
std::thread::scope(|s| {
    for _ in 0..5 {
        s.spawn(|| println!("{:?}", data));
    }
});

21.2 thread local storage
#

使用 thread_local!() 宏来创建 static LocalKey<T> 类型全局常量(具有内部可变性)。

  • LocalKey<T> 提供了 get/set/with() 等方法,参数都是 &self, 所以 LocalKey 的泛型参数类型 T 需要实现内部可变性 ,一般使用 Cell/RefCell 来包裹(因为是线程本地存储,所以不会有并发问题)。
    • get() 返回 T,T 需要实现 Copy。
    • take() 返回 T,T 需要实现 Default。
  • LocalKey 只允许共享引用 &T, 为了获得可变引用 &mut T, 需要使用 Cell/RefCell;
  • Rust 为 LocalKey<RefCell<T>> 提供了特殊支持:with_borrow() 和 with_borrow_mut()
use std::cell::RefCell;
use std::thread;

thread_local!(static FOO: RefCell<u32> = RefCell::new(1)); // FOO 类型需要支持内部可变性

FOO.with_borrow(|v| assert_eq!(*v, 1));
FOO.with_borrow_mut(|v| *v = 2);

// 每个线程都有自己的 FOO 对象

let t = thread::spawn(move|| {
    // 子线程的 FOO 对象
    FOO.with_borrow(|v| assert_eq!(*v, 1));
    FOO.with_borrow_mut(|v| *v = 3);
});

t.join().unwrap();

// 主线程的 FOO 对象
FOO.with_borrow(|v| assert_eq!(*v, 2));

22 std::net
#

std::net::ToSocketAddrs trait 用于实现域名解析,它的 to_socket_addrs() 方法返回解析后的 IP:

use std::net::{SocketAddr, ToSocketAddrs};
// assuming 'localhost' resolves to 127.0.0.1
let mut addrs_iter = "localhost:443".to_socket_addrs().unwrap();
assert_eq!(addrs_iter.next(), Some(SocketAddr::from(([127, 0, 0, 1], 443))));
rust-crate - 这篇文章属于一个选集。

相关文章

tokio
··23918 字
Rust Rust-Crate
Tokio 是 Rust 主流的异步运行时库。它提供了异步编程所需要的所有内容:单线程或多线程的异步任务运行时、工作窃取、异步网络/文件/进程/同步等 APIs。
anyhow
··1816 字
Rust Rust-Crate
anyhow crate 提供了自定义 Error 类型和 Result 类型,Error 类型自带 backtrace 和 context,支持用户友好的格式化信息输出。
bytes
··2834 字
Rust Rust-Crate
bytes 提供了高效的 zero-copy 连续内存区域的共享和读写能力。
chrono
··4003 字
Rust Rust-Crate
chrono 提供了丰富的 Date/Time 类型和相关操作。