Rust前世今生

Posted by LXH on August 7, 2025

1.类型安全

维度 C C++ Rust 的设计保障
内存管理 malloc/free 手动管理,易泄漏、悬垂指针、重复释放 new/delete 也存在悬垂、资源泄漏(RAII 缓解但依赖程序员习惯) 所有权 (Ownership) + 借用检查 (Borrow checking),编译期强制保证:资源只能有一个所有者,作用域结束自动释放
指针安全 原指针可随意解引用,可能产生野指针、悬垂指针 同 C,增加了智能指针 不允许随意解引用原指针,unsafe 代码块被强制标记;默认使用 &T / &mut T 的安全引用
类型转换 void* 可强制转换为任意类型,导致UB 隐式转换规则复杂 没有 void*,强制类型安全;转换必须显式 (as),且很多情况需要 unsafe
数组与缓冲区 无边界检查,strcpy, gets 易导致缓冲区溢出 同 C,STL 容器安全性较高,但 T[] 依旧危险 数组和 Vec 访问必须在边界内 (index 会 panic,get 返回 Option)
空指针 NULL 引用随处可能产生崩溃 nullptr 改善了语义,但依然可能被解引用 没有空指针,使用 Option<T> 明确表示“可能为空”
未初始化变量 可使用未初始化内存,导致 UB 局部变量不自动初始化,未定义行为 所有变量在使用前必须初始化,编译器检查
异常/错误处理 返回码 try/catch 无异常机制,采用 Result<T, E> 明确错误类型,编译期强制处理或传播
泛型/多态 void* 实现泛型,非常不安全 多态依赖虚函数指针,可能悬垂 单一机制:零成本抽象(泛型 + Trait bound),无虚函数开销;编译期保证类型一致
类型安全问题
  • C/C++ 典型的类型安全问题主要来自
    • 指针不安全(悬空、野指针、void*、函数指针乱用)
    • 内存访问不安全(越界、溢出、释放后访问)
    • 类型系统过于宽松(隐式转换、强制转换、union、对象切片)
    • 未定义行为(UB)泛滥
  • 举例:野指针
    int *p;
    {
      int x = 10;
      p = &x;
    } // x 已经销毁
    printf("%d\n", *p); // UB
    

    Rust是静态类型语言,在编译时会进行类型检查。同时支持类型推断,因此不需要显示标注每个变量的类型。但是,不允许隐式的类型转换,必须显式的进行。

  • 强类型
    • 编译时严格检查不同类型之间的兼容性,禁止隐式转换
  • 静态类型
    • 每个变量在编译时有且只有一个类型,运行时不可改变类型
  • 类型大小
    • 编译器必须知道变量在栈上的大小(编译期固定)。
      • 大部分类型是 Sized(大小在编译期确定,如 i32, bool, struct)。
      • 有些类型是 !Sized(大小未知,如str[T]dyn Trait)。
        • str 不定长,let s: str;:x:。必须通过指针来间接使用,如 &strBox<dyn Trait>
        • Rust 内置 Sized trait,默认泛型参数要求 T: Sized
    • 👉 这个机制保证了内存布局可知,防止C中存在的野指针或数组越界问题。
  • 类型推导
    • 编译器尽量从右边的值推导出左边变量的类型。
    • 如果推导不出,必须显式标注。
枚举类型
Rust中,若预计某变量值在某时刻可能为空值,则尽量用Option/Result定义它

。为此设定了enum Optionenum Result两个枚举类型。

  • Option

    std::option 表示一个可选值

// 每个option要么是一个some中包含一个值
// 要么是一个None
pub enum Option<T>{
    None,
    Some(T),
}
// 由此,一旦某变量不是Option类型的 
// 我们则认为它的值永远不会是空  
  • Result

    std::result::Result Result很常用,直接被包含到prelude中 因此不需要手动引入这个包

enum Result<T, E>{// 泛型T表示正确执行后返回值的类型,E表示异常时存放的错误值 
  Ok(T), // 返回值的存放方式为Ok(T)
  Err(E),// 返回值的存放方式为Err(E)
}

2.内存安全

方面 SaferCPlusPlus Rust
越界访问 容器、视图类型自带边界检查 编译时保证 + 部分运行时检查
悬垂指针 用更安全的智能指针 / view 类型替代原生指针 编译时借用检查
未初始化变量 safer int/bool 类型避免使用未初始化值 Rust 默认禁止未初始化,必须通过 MaybeUninit 显式声明
Use-after-free 通过 safer 指针/引用降低风险 Rust 编译器禁止,生命周期系统强制约束

所有权系统ownership

谁能管理资源

  • 每个值都有一个所有者(owner)。
  • 在同一时刻,值只能有一个所有者。
  • 当所有者离开作用域时,值会被自动销毁(drop)。

借用检查borrow checking

谁能临时使用资源

  • 不可变引用&
    • 只读访问数据,不能修改引用指向的数据
  • 可变引用&mut
    • 允许修改数据
  • 同一时间只能有一个可变引用,且不能与不可变引用同时存在

智能指针&RAII

智能指针

相对于“裸指针”:把生命周期管理、引用计数、并发锁机制都包装在类型里。

  • Box:在堆上分配数据,所有权在 Box 上。
  • Rc:**单线程**场景下实现引用计数。
  • Arc:**多线程**场景下实现引用计数。
  • RefCell / Cell:在运行时执行借用检查(内部可变性)。
  • Mutex / RwLock:并发场景下的智能指针,保证线程安全。
    RAII

    资源获取与生命周期绑定 在rust中,变量不仅指代相应的内存,也拥有资源(如示例中的文件资源)。

// 示例代码
let wordlist_file = File::open(&args[1])?;

❓ 文件操作只有open,没有close

  • 当超出作用域时,对象会自动调用析构函数,释放内存
    • 这里的文件句柄wordlist_file的作用域为main函数,当main函数返回时,文件会自动关闭

生命周期lifetime

编译器需要知道引用的数据在内存中存活的时间,以保证引用在有效范围内是安全的
如果编译器无法推断出引用的生命周期,则需要显式提供生命周期注释

  • 用法:'+标识符,如'a,通常放在函数签名或结构体的定义中

生命周期注释降低了可读性;在复杂共享环境下,可结合智能指针std::rc::Rc(reference counting)

  • Rc单线程场景下实现引用计数
  • Arc多线程场景下实现引用计数

注意:引用计数天然的存在循环引用问题。

3.并发安全

方面 SaferCPlusPlus Rust
并发模型 提供 safer 共享类型,避免常见 data race Send/Sync trait + 所有权,编译期保证无数据竞争
安全等级 较高(但依赖库封装) 最高级别:编译时阻止错误发生
  • Send & Sync trait
    • Send:是否可以在线程间传递所有权
    • Sync:能否在多个线程中安全的共享
  • 基于共享状态的并发
    • 并发原语
      • 【读共享】Arc<T>:多线程共享不可变数据(只读)。
      • 【写共享】Arc<Mutex<T>>:允许多线程共享可变数据(加锁保证安全)。
      • 【写共享】Arc<RwLock<T>>:允许读多写一的共享访问。
      • 【写共享】Atomic<T>:对一些基础类型提供无锁共享修改.
  • 基于消息传递的并发Channel
    • std::sync::mpsc标准库(multi-producer, single-consumer)
    • “共享内存的替代方案”:不要用共享内存来通信,而用通信来共享内存。
      • “通信/消息传递”:基于所有权转移的线程通信。
  • 所有权

go中的channel:语言特性、语言内置的管道机制 Rust中的channel:类型安全+所有权转移的通信管道

0.编译器静态检查

系统编程语言发展趋势

系统编程语言的选择质量维度:安全、性能、开发效率

0. 内存安全风险

  • 关于安全类型库SaferCplusplus
    • 提供某些标准库、标准容器、数据类型的安全替代方案;
    • 提供多线程环境的安全共享类型,减少并发错误、数据竞争;
    • 提供指针和引用的安全替代(智能指针);
    • 内置逻辑检查;可选的静态检查器;
  • 关于静态分析器scptools

1. safe CPP

关于safecpp proposal:2024-09-11,Sean Baxter、Christian Mazakas提出的“C++安全扩展提案”

不是传统意义上的库
也不是对现有标准库的简单替代
而是语言层面的提案,旨在通过定义一个“安全子集(safe context)”确保内存安全、线程安全、生命周期安全等特性,同时尽量保持C++的性能和兼容性。 需要编译器层面的支持。

  • 实现C++超集,这个超集具有一个安全子集(排除undefined behavior)。
  • 安全上下文与不安全上下文划清界限。
  • 安全子集必须是有用的。需要提供安全的替代方案来取代不安全技术,比如用类型选择和借用取代unions和pointers
  • 兼容现行代码。

2. fil-C

关于fil-C

  • invisiCAP pointer不可见权限指针(64bits)==普通指针(64bits)+(访问边界+[读/写/执行]权限)(不会显式存储在内存中)
  • FUGC(Fil’s unbelievable garbage collection)==并行mark-and-sweapGC
  • fil-c ==编译器+运行时检查系统
    • 实质:编译现有的C代码,并在运行时检查无效的内存访问
    • 问题:性能

3. C-CPP-Rust混合编程(三分天下)

Rust 是一种可以提供严格的内存安全的主流系统级、非垃圾回收语言。但 C++ 和 Rust 具有不同的设计,互操作能力有限,这使得从 C++ 到 Rust 的增量迁移成为一个艰苦的过程:Rust 缺乏函数重载、模板、继承和异常。C++ 缺乏traits、重定位relocation和借用检查borrow-checking。因此,通过在 Rust 中重写关键部分来强化 C++ 应用程序很困难。

特性 C++ SaferCPlusPlus Rust
内存安全 ❌ 无内建保障,易出现悬垂指针、缓冲区溢出 ⚠️ 部分保障,通过 safer 类型(如 nii_array, safe_iterator)减少错误 ✅ 编译期借用检查 + 所有权模型,系统级内存安全
线程安全 ❌ 共享内存和数据竞争常见 ⚠️ 提供 safer 容器和同步工具,避免部分数据竞争 ✅ 编译器强制检查,数据竞争在编译期报错
运行时开销 ✅ 无额外开销 ⚠️ 较低(主要是额外的检查和 safer 类型包装) ⚠️ 一般与 C++ 持平
兼容性 ✅ 完全兼容现有 C++ ✅ 基于 C++11/14 标准库扩展,现有项目能渐进迁移 ❌ 语法和编译模型完全不同,需要重写
迁移成本 ✅ 无成本 ⚠️ 中等:逐步替换不安全类型 ❌ 高:需要大规模重构或重写
目标定位 系统级性能和灵活性 在 C++ 中引入更安全的默认实践 从语言层面彻底解决安全问题