Skip to content

Instantly share code, notes, and snippets.

@fwqaaq
Last active January 9, 2024 14:52
Show Gist options
  • Save fwqaaq/089b6b4abc8b3b2ec130ed851a49fb1b to your computer and use it in GitHub Desktop.
Save fwqaaq/089b6b4abc8b3b2ec130ed851a49fb1b to your computer and use it in GitHub Desktop.
Rust 常用概念

零成本抽象

Rust 的抽象是零成本的,Rust 的抽象并不会存在运行时性能开销,这一切都是在编译期完成的。(零成本抽象的基石是范型和 trait)

孤儿规则

当你为某类型实现某 trait 的时候,必须要求类型或者 trait 至少有一个是在当前 crate 中定义的。 你不能为第三方的类型实现第三方的 trait 。

鸭子类型

鸭子类型(duck typing),简单来说,就是只关心值长啥样,而不关心它实际是什么。当一个东西走起来像鸭子,叫起来像鸭子,那么它就是一只鸭子,就算它实际上是一个奥特曼,也不重要,我们就当它是鸭子。

FFI

FFI(Foreign Function Interface)是用来与其它语言交互的接口,在有些语言里面称为语言绑定(language bindings),Java 里面一般称为 JNI(Java Native Interface) 或 JNA(Java Native Access)。

CTFE

Rust 编译器也可以想 C++ 或 D 那样,拥有编译时函数执行(Compile Time Function Execution)。

const fn init_size() -> usize {
    5
}

DST

  • 定长类型(sized):这些类型的大小在编译时是已知的
  • 不定长类型(unsized)与定长类型相反,它的大小只有到了程序运行时才能动态获知,这种类型又被称之为 DST

Rust 中常见的 DST 类型有: str[T]dyn Trait,它们都无法单独被使用,必须要通过引用或者 Box 来间接使用 。

Pointer Type

pub(crate) struct PtrComponents<T: ?Sized> {
    //*const ()保证元数据部分是空 
    pub(crate) data_address: *const (),
    //不同类型指针的元数据
    pub(crate) metadata: <T as Pointee>::Metadata,
}
pub trait Thin = Pointee<Metadata = ()>;

Rust 中对于固定大小类型的指针实现了 Sized Trait,Rust 定义这种类型为瘦指针(thin pointer),类型为 (),元数据大小为 0。

  • 对于动态大小类型(DST),Rust 定义为胖指针(fat pointer 和 wide pointer),元数据为以下:
    • 结构体类型,如果最后一个成员是 DST(结构体的其他成员不能是 DST),则元数据为此 DST 类型。
    • str 类型,元数据是按字节计算的长度值,元数据类型是 usize
    • 切片类型,例如 [T],元数据是数组元素的数目值,元数据类型是 usize
    • trait 对象,例如 dyn SomeTrait, 元数据是 DynMetadata<Self>DynMetadata<dyn SomeTrait>
//dyn trait 裸指针的元数据结构,此元数据会被用于获取 trait 的方法
pub struct DynMetadata<Dyn: ?Sized> {
    //在堆内存中的 VTable 变量的引用
    vtable_ptr: &'static VTable,
    phantom: crate::marker::PhantomData<Dyn>,
}

'static&'static 区别

&'static 表示的是引用本身必须和剩下的程序活得一样久,'static 表示的是引用所持有内容的生命周期将会和程序活得一样长(例如 trait&str 都是 'static)。

参考:https://course.rs/advance/lifetime/static.html#static

VTable

在 Rust 中,虚函数表(vtable)是一种用于支持动态分发(dynamic dispatch)和动态类型(dynamic typing)的数据结构。vtable 通常与 trait 对象(trait object)一起使用。

//此结构是实际的 trait 实现
struct VTable {
    //trait 对象的 drop 方法的指针
    drop_in_place: fn(*mut ()),
    //trait 对象的内存大小
    size_of: usize,
    //trait 对象的内存对齐
    align_of: usize,
    //后继是 trait 对象的所有方法的指针数组
}
  • 当你创建一个 trait 对象时,例如 Box<dyn Trait>&dyn Trait,这个对象实际上包含两部分信息(见之前的 DynMetadata):
    • 数据指针:这是一个指向实现了特定 trait 的数据的指针。
    • vtable:这是一个包含了与特定 trait 相关的函数指针的表。

动态分发:当你通过 trait 对象调用一个方法时,Rust 会查找 vtable,找到对应的函数指针,然后调用这个函数。具体调用哪个函数是在运行时决定的,而不是在编译时决定的,所以会有一定的运行时开销。

补充:静态分发(Static Dispatch)是一种在编译时确定函数调用的机制。这是通过使用泛型和特性(traits)实现的。即当你定义一个使用泛型参数的函数或方法,并且这个参数被约束为实现了某个特性,Rust 编译器会为每一种具体的类型生成一个函数或方法的实例。

RAII

在构造函数中获取资源(比如申请内存、打开文件等),在析构函数中释放资源(比如释放内存、关闭文件等),这样可以确保资源在对象生命周期结束时自动被释放,防止资源泄露。

Rust 中通常的释放资源是通过 Drop 方法:

struct Person {
    name: String,
}

impl Person {
    fn new(name: String) -> Person {
        Person { name }
    }

    fn use_resource(&self) {
        println!("use resource,{}", self.name)
    }
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("Release resource")
    }
}

unsafe 语法

unsafe 是指在以下五种操作时,并不会提供任何安全检查操作

  • 解引用裸指针
  • 调用一个 unsafe 或外部的函数
  • 访问或修改一个可变的静态变量
  • 实现一个 unsafe trait
  • 读写 union 联合体的字段

unsafe 和 safe 以下三方面的区分

  • unsafe rust 不需要安全检查,有一定性能提升。
  • unsafe rust 内存安全完全交由开发者来保证,可能会出现未定义行为。
  • 区分编译器和开发者的指责,如果代码先出问题,可以先排查 unsafe rust。

基于 Unsafe 使用的原始指针

  • 在需要跳过 Rust 安全检查的时候。(在程序不会有任何问题的时候,使用原始指针避免不必要的安全检查)
  • 与 C 进行 ffi 的时候。

trait

  • trait 限定:可以将任意类型的范围根据类型的行为限定到更精确可控的范围。
  • trait 对象:将共同拥有相同行为的类型集合抽象为一个类型。并且 trait 对象只有在满足下面两条规则下才可以作为 trait 对象:
    • trait 对象的 Self 类型参数不能被限定为 Sized。
    • trait 中所有的方法都必须是对象安全的。
#[derive(Debug)]
struct Foo;

trait Bar {
    fn baz(&self);
}

impl Bar for Foo {
    fn baz(&self) { println!("{:?}", self); }
}

fn static_dispatch<T>(t: &T) where T: Bar {
    t.baz();
}

fn dynamic_dispatch(t: &dyn Bar) {
    t.baz();
}

fn main() {
    let f = Foo;
    static_dispatch(&f);
    dynamic_dispatch(&f);
}
  • 对象安全:
    • 方法受 Self: Sized 约束。
    • 方法签名同时满足以下三点:
      1. 必须不包含任何泛型参数。如果有泛型,trait 对象在虚表(VTable)中查找方法时不确定调用哪个方法。
      2. 第一个参数必须为 Self 类型或可以解引用为 Self 的类型(必须有接受者,例如 self、&self、&mut self 和 self: Box<Self>,没有接受者的方法对 trait 对象毫无意义)
      3. Self 不能出现在除第一个参数之外的地方,包括返回值。这是因为如果出现 Self,那就意味着 Self 和 self、&self 或 &mut self 的类型相匹配。但是对于 trait 对象,根本无法做到保证类型匹配。

数据并行

  • SISD:单指令单数据的单 CPU 机器,它在单一的数据流上执行指令。(任何单核 CPU)
  • MISD:在多个 CPU 对单个数据流执行多种不同的指令。有多个指令来操作同一组数据。(很少被用到)
  • SIMD:一个指令操作多个数据元素。这种架构通常用于数据并行的情况,即相同的操作需要应用于大量数据。SIMD 构在图形处理(如 GPU)、科学计算和音视频处理中非常普遍
  • MIMD:允许多个 CPU 同时执行不同的指令集,每个指令集作用于不同的数据集。这是最灵活的并行架构,被广泛应用于多处理器系统,如超级计算机和分布式计算系统。在 MIMD 系统中,每个 CPU 可以独立地工作,执行不同的任务。这种架构非常适合于复杂的、多任务的并行处理。

String、&str、Vec 和 &[u8] 的惯用转换

原始值 转换值 转换条件
&str String String::from(s) / s.to_string() / s.to_owned()
&str &[u8] s.as_bytes()
&str Vec s.as_bytes().to_vec() / s.as_bytes().to_owned()
String &str &s / if possible* else s.as_str()
String &[u8] s.as_bytes()
String Vec s.into_bytes()
&[u8] &str std::str::from_utf8(s.to_vec()).unwrap() ✅
&[u8] String String::from_utf8(s).unwrap() ✅
&[u8] Vec s.to_vec() / s.to_owned()
Vec &str s.as_slice()
Vec String std::str::from_utf8(s).unwrap() ✅
Vec &[u8] String::from_utf8(&s).unwrap() ✅
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment