Rust 笔记

一句话笔记

  • 用 rustup 脚本进行 Windows 平台 MSVC ABI 安装时,可以选择仅下载安装构建工具 Microsoft C++ Build Tools仅安装所需组件
  • const 是内联常量;而 static 是静态变量,在内存中独一份
  • 表达式优先级:: > . > 数组访问 > ? > * & &mut > as > 四则运算 > ……
  • ? 返回时会调用 Errorinto() 方法以匹配返回类型
  • 共享引用 &T 实现了 Copy,而可变引用 &mut T 没有
  • T 作为泛型标注不仅包含 所有权类型 也包含 共享引用可变引用 ,但这两种引用类型互不包含来源
  • mod 关键字用于将其后的模块导入,既可以行内、也可以通过同名文件或同名文件夹下的 mod.rsuse 关键字创建 已导入 的模块内容的“快捷方式”。来源

  • fnFn,前者是函数指针、后者是一个 Trait;这意味着前者为编译时的函数,后者多为闭包。

  • 诸如 fn foo(t: impl Trait) 中的 impl Trait 相当于泛型参数,但有限制 来源

  • 对于形如 ident<T> 的泛型声明(ident可为函数、方法、结构体、枚举等等),我们往往可以 ident::<T> 来指定泛型参数,此语法被称作“涡轮鱼”。 来源

  • BorrowAsRef 两 trait 的区别在于:前者表示二者本质相同(哈希一致,比如 String&str),后者不然(String[u8])。 来源

  • CloneCopy 的前置 trait,前者允许被复制,后者使复制时常发生 来源

  • 标注在函数与 impl 上的生命周期,都表示这个生命周期来自函数作用域外,说明带有这个生命周期的引用、即便超出函数作用域还能存活;而来自于函数内的引用往往在函数退出时便失效了。来源

  • 使用

    1
    RUSTFLAGS='-C target-feature=+crt-static' cargo b -r --target x86_64-unknown-linux-gnu

    静态编译Linux下的二进制文件。 来源

  • Rust 可以应用设计模式,见 Rust Design Patterns

  • 当表达对象间复杂关系时,往往 Entity Component System 更加适合 Rust。 来源1 来源2

  • Rust 的迭代器失效问题(iterator invalidation)经常会被编译器指出

  • 关于如何长期维护 Rust 项目

  • Rust Cheat Sheet

  • Rust 容器类型在内存中的样子

长一些的知识点

杂项

  • dyn Trait动态分发

    又称 trait object;由于一种 Trait 对不同类型的实现(impl)可能不同,我们便可以使用 dyn 来表示这些类型,例如 Box<dyn Debug>(不同实现大小不同,故需要放在堆上)

    dyn Trait 在内存中表现为一个胖指针,其 data 段存储一些数据(例如 虚表 的容量、长度等等),vtable 则是指向 虚表(别名 虚函数表vtable虚表vftable……)的指针;虚表 存储了不同类型的实现

    对比 泛型

    • 泛型 将所有实现的可能性枚举出来并编译,故称为 静态分发
    • trait object 则将实现储存在内存中按需取用,故称为 动态分发

    大多数情况下,泛型 使运行速度更快、编译更慢,trait object 则相反、且写起来更“精简”

    但是 trait object 要求 Trait 对象安全

    更多 更多关于 对象安全

  • 'static 的两种意思

    • 作为生命周期(例如 let s: &'static str = ……static s: u8 = ……)时,表示 s 活得比 'static 长,即在整个程序运行期间都将存在
    • 作为 trait 约束(例如 …… where T: 'static ………… t: impl 'static ……) 时,表示 T 不接受活得比 'static 短的引用,即既接受生命周期至少为 static 的引用,又接受非引用(所有权)类型

    以上内容可以拓展至任意'a 来源

  • 位置表达式与值表达式左值右值):

    • 位置表达式,表示内存位置(该位置一般存了)。仅表现为:局部变量、静态变量、*exprexpr[...]expr.f 几种表达式
    • 值表达式,表示。非位置表达式皆为值表达式

    通常地,我们称代码中书写表达式的地方为上下文;结合两种表达式的来历,就可以得知存在两种上下文

    • 当一个 上下文 需要内存中的位置,它便是 位置表达式上下文
      仅表现为:赋值左侧、借用(含隐式借用)、被索引、被访问字段、let ... = 右侧、
      if/while let ... =match 右侧
      .. 右侧
      的操作数
    • 当一个 上下文 需要值,它便是 值表达式上下文。非位置表达式上下文皆为值表达式上下文

    那么自然地,需要研究表达式与上下文的结合:

    • 值表达式值表达式上下文 中,没问题
    • 位置表达式位置表达式上下文 中,也没问题
    • 位置表达式值表达式上下文 中,没什么大不了,将此位置的值拿来用就好了
    • 值表达式位置表达式上下文 中,看情况,有时坚决不行、有时能用临时值凑合

    参考

  • move 与 copy 的条件:当 位置表达式值表达式上下文 中,若位置表达式实现了 Copy,则其值会被 复制(copy 出来;若没有,但其实现了 Sized,则其值会被 移出(move 原位置表达式(移出后该位置将无法被访问),移出条件:

    • 位置表达式未被借用
    • 位置表达式是临时值
    • 位置表达式表现为对 Box<T> 的解引用,且里面的值可被移出
    • 位置表达式被访问字段,且该字段未实现 Drop 且能被移出
  • 析构

    • 离开一个作用域时,其中变量与临时值会被 按序 调用其 Drop::drop 而后析构
    • 赋值语句的左侧也会被调用其 Drop::drop 再析构
    • 注意!let _ = expr; 中,如果 expr 返回了一个临时值,则此变量会被立即析构。 来源1 来源2

隐式行为

  • 调整 rust-analyzer 的 Inlay Hints 设置似乎可以间接查看一些隐式行为的发生

  • 解引用强制转换

    对于实现了Deref<Target = U>T 与其实例 t ,且 U 类型有对应t的实例 u,存在以下隐式行为

    • 使得 *t等效于_(&t).deref(),得到 u_ (deref()的定义:接收&self并返回&Target类型)(这里发生了查找方法调用*)

    • 允许 &t 强制转换为 &u

      rust-analyzer 显示的 &String&str 的过程:已知 String 实现了 Deref<Target = str> ,那么 s: &String 可以通过 *s 可直接解为String ,再 *(*s) deref 至 str,最终 &(*(*s)) 后得到 &str

    DerefMut 性质相似

  • 类型强制转换

    这是一个隐式行为;此行为在 let const static 声明、结构体初始化、函数传参、函数返回 时若 实际值的类型与声明类型不匹配 就会触发——编译器将尝试对此值不断进行:

    • 可变转为不可变
    • 对容器类型的内容进行类型强制转换
    • ……

    直到匹配声明类型

    此隐式行为亦可用 as 显式触发

  • 自动解引用

    这是一个隐式行为;访问结构体struct的字段(比如struct.field)时,若结构体实现了DerefDerefMut,则它将被尽可能多地解引用直到能够访问此字段

  • 查找方法调用

    这是一个隐式行为;在调用结构体方法(这里以Box<[i8; 8]>.zip()为例)时,会先 被尽可能多地解引用 并在最后 尝试非固定尺寸类型自动强转 以构建一个列表 :

    1. Box<[i8; 8]>(结构体本身)
    2. [i8; 8](解引用)
    3. [i8](非固定尺寸类型自动强转)

    再按列表顺序添加每项的T&T&mut T

    1. Box<[i8; 8]>
    2. &Box<[i8; 8]>
    3. &mut Box<[i8; 8]>
    4. [i8; 8]
    5. &[i8; 8]
    6. &mut [i8; 8]
    7. [i8]
    8. &[i8]
    9. &mut [i8]

    最后按列表顺序查找 可见zip方法,且优先查找直接 impl 的方法、然后才查找 trait 中的方法

  • reborrow

    这是一个并未被详细说明的隐式行为;当且let 声明、函数形参等等 显式要求传入可变引用(形如 _: &mut T)时,编译器不会移动已有可变引用 t: &mut T (可变引用没有实现Copy),而是通过 &mut *t 创建新的可变引用并传入,此时原引用被“锁定”直到新引用失效

    编译器有时也会自动通过 &*t 创建新的共享引用

  • 生命周期省略

    这是一个隐式行为,编译器会在某些情况下自动添加生命周期标识,所以一般仅在函数(不适用于闭包)有 多于一个传入引用且返回引用 时需要标注生命周期

Unsafe 相关

  • 子类型
    由于 长生命周期'long 往往可以应用到需要 短生命周期'short 的地方,即 “长周期 是 短周期”,于是我们定义较长周期是较短周期的子类型,由此可得'static是所有生命周期的子类型

其他资料