惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
C
Cybersecurity and Infrastructure Security Agency CISA
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
T
Threat Research - Cisco Blogs
C
Cisco Blogs
Recent Announcements
Recent Announcements
S
Securelist
N
Netflix TechBlog - Medium
The Register - Security
The Register - Security
P
Privacy & Cybersecurity Law Blog
宝玉的分享
宝玉的分享
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 热门话题
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
月光博客
月光博客
AWS News Blog
AWS News Blog
P
Proofpoint News Feed
博客园 - 司徒正美
L
LINUX DO - 最新话题
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
H
Help Net Security
Spread Privacy
Spread Privacy
PCI Perspectives
PCI Perspectives
Project Zero
Project Zero
I
Intezer
T
The Blog of Author Tim Ferriss
有赞技术团队
有赞技术团队
The Last Watchdog
The Last Watchdog
C
Check Point Blog
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
MyScale Blog
MyScale Blog
V
Vulnerabilities – Threatpost
Recorded Future
Recorded Future
T
Tenable Blog
Jina AI
Jina AI
D
DataBreaches.Net
阮一峰的网络日志
阮一峰的网络日志

博客园 - PKICA

博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 rust线程-std::thread::park和unpark 配合Builder实现轻量级的线程挂起与唤醒 rust自定义线程属性std::thread::Builder rust线程 rust关联函数 rust高并发设计实践进阶 rust高并发设计实践 rust性能优化与安全边界 联合体union在rust和C语言中有什么区别 rust并发与异步编程 如何预防rust不良的代码设计总结 rust类型系统与零成本抽象 告别大显存依赖!用 Rust 新一代深度学习框架 Burn 打造纯 CPU 文本分类推理引擎 rust类型系统标记 编译配置解答 git实用命令 rust底层设计理念值得注意的几个地方总结 rust可变引用作为函数参数的机理详解 Rust内存重解释transmute C与Rust类型映射 Rust FFI 安全抽象范式 rust延迟初始化原语 rust重借用机制与原理 rust参数传递模型 汇编语言语法详解 gdb汇编调试 gdb-pwndbg的安装与使用指南 gdb调试插件gef C语言thread_local linux系统readelf命令使用指南 gcore转储进程内存 gdb查看命令 RGB与YUV颜色编码的区别 Rust原子类型 C++ STL求两个集合交集差集 gdb调试集锦 ubuntu24.0.4使用root用户登录 ubuntu24.0.4输入密码后跳回登录界面 AI内存压缩技术TurboQuant及存疑 ubuntu切换到指定内核版本 在没有顶级科技大佬直接背书的情况下deepseek为啥能够异军突起? HuggingFace和deepseek的关系 当前主流AI大模型 Rust写时克隆Cow系列2
rust内存模型
PKICA · 2026-07-22 · via 博客园 - PKICA

如果你已经理解了 Rust 的底层设计哲学,恭喜你,你可以忽略本篇所讲的主旨了。

问题一:当非 Copy 变量(如 String)所有权转移时,在 CPU 和内存层面发生了什么?

1. 内存层面的真实变化

  • 栈(Stack)上的“浅拷贝”:非 Copy 类型(如 String)在栈上是一个固定大小的结构体,包含三个字段:堆内存指针(Pointer)、当前长度(Length)和容量(Capacity)。发生 move 时,CPU 会将这 3 个机器字(通常是 24 字节)从旧变量的栈空间复制到新变量的栈空间。
  • 堆(Heap)上毫无变化:分配在堆上的实际文本数据保持原位,不会发生任何拷贝。

2. 编译期的“魔法”(防止双重释放)

C++ 中若只复制指针,两个变量析构时会释放同一块堆内存(Double Free)。Rust 解决这个问题的核心在编译器,而非运行时:

  • 流分析(Dataflow Analysis):编译器在编译时会追踪变量的状态。一旦发生 move,旧变量在语义上就被标记为“未初始化/冻结”状态。
  • 零运行时开销:如果代码后面试图再次读取旧变量,编译器直接报错拒绝编译。在生成的机器码中,旧变量的析构函数(Drop)会被直接抹去(或通过条件标志位跳过),因此在运行时只有新变量在离开作用域时才会调用一次 Drop,确保绝对安全且没有任何多余开销。

问题二:有了单线程的 RefCell,为什么多线程需要 Mutex?强行用会怎样?

1. 两者的本质区别

  • RefCell<T>(运行时借用检查):它不提供任何并发锁机制。它只是把 Rust 的“不可变借用”和“可变借用”规则,从编译期推迟到了运行期。
  • Mutex<T>(原子级并发锁):它通过操作系统底层的互斥锁(以及 CPU 的原子指令)来确保在任何绝对的时间片内,有且仅有一个线程能够访问内部的数据。

2. 为什么多线程不能用 RefCell

因为 RefCell 内部用来记录“当前有多少个借用引用”的计数器是一个普通的整数(非原子类型)。

  • 如果强行在多线程中使用,当两个线程同时尝试修改这个计数器时,会发生数据竞争(Data Race)。
  • 这会导致计数器损坏,从而让两个线程同时获取到内部数据的可变引用(&mut T),彻底破坏 Rust 的内存安全基石,引发未定义行为(Undefined Behavior)。

3. Rust 如何在编译期阻止这种危险?

Rust 拥有两个自动特质(Auto Traits):SendSync

  • RefCell<T> 内部的借用计数器不是线程安全的,因此 RefCell<T> 没有实现 Sync
  • 由于没有 Sync,你就无法将 &RefCell<T> 跨线程共享。如果你尝试用 Arc<RefCell<T>> 包裹它并传给新线程,编译器会直接报错,在编译阶段就把这种多线程隐患彻底掐死。

问题三:'static 用作引用(&'static str)和用作泛型约束(T: 'static)时,分别代表什么?

这是 Rust 中极易混淆的概念,它们虽然共享同一个名字,但语境完全不同。

1. 用作引用:&'static str / &'static T(数据的生命周期)

  • 含义:它指的是被指向的数据在整个程序的运行期间都存活,且永远不会被释放。
  • 经典场景:硬编码在代码里的字符串字面量(存储在编译产物的 .rodata 只读数据段中),或者通过 Box::leak 故意泄漏、使其永久常驻内存的对象。

2. 用作泛型约束:T: 'static(类型的生命周期能力)

  • 含义:它约束的是类型 T 本身必须有能力活得和程序一样久。这意味着:类型 T 内部绝对不能包含任何带有“短生命周期”的借用引用。
  • 通俗理解:
    • String 拥有其内部数据的所有权,不依赖任何外部借用。只要你不主动销毁它,它想活多久就能活多久。所以 String 满足 T: 'static
    • &'a String 内部包含一个生命周期为 'a 的外部引用。它的存活受限于 'a。一旦 'a 结束,这个引用就失效了。因此它不满足 T: 'static(除非 'a 本身就是 'static)。

3. 为什么多线程派生(如 tokio::spawn)强制要求 T: 'static

因为当你把一个任务交给另一个线程执行时,主线程可能随时结束并销毁自己的栈帧。如果新线程里的类型 T 携带了对主线程栈上数据的引用(非 'static),主线程一死,新线程就会访问到悬空指针。强制要求 T: 'static 确保了传入线程的数据要么是完全拥有所有权的(如 Stringi32),要么是永久合法的,彻底绝育了跨线程悬空指针。

反例一:强行在多线程间共享 RefCell

这个例子展示了为什么不能把 RefCell 传给其他线程。我们用 Arc(原子引用计数)来尝试跨线程克隆指针,但内部依然使用单线程的 RefCell

1. 错误代码

use std::cell::RefCell;
use std::sync::Arc;
use std::thread;

fn main() {
    // 创建一个被 Arc 包裹的 RefCell
    let data = Arc::new(RefCell::new(42));

    let data_clone = Arc::clone(&data);
    
    // 尝试开辟新线程,并在新线程中修改 RefCell 内部的值
    thread::spawn(move || {
        let mut val = data_clone.borrow_mut();
        *val += 1;
    });
}

2. 编译器报错信息(核心部分)

error[E0277]: `RefCell<i32>` cannot be shared between threads safely
   --> src/main.rs:12:5
    |
12  |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `RefCell<i32>` cannot be shared between threads safely
    |
    = help: within `[closure@src/main.rs:12:19: 12:26]`, the trait `Sync` is not implemented for `RefCell<i32>`
    = note: required because it appears within the type `Arc<RefCell<i32>>`
note: required by a bound in `spawn`

3. 报错深度解析

  • the trait Sync is not implemented for RefCell<i32>:这是最关键的一句。Rust 编译器明确指出 RefCell 没有实现 Sync 这一特质。
  • Sync 导致闭包无法 Sendthread::spawn 要求传入的闭包必须实现 Send。而闭包捕获了 Arc<RefCell<i32>>。根据 Rust 的多线程规则:只有当 T: Sync 时,Arc<T> 才是 Send 的。由于 RefCell 不是 Sync,导致 Arc<RefCell> 不是 Send,最终闭包不是 Send,编译直接被拦截。

反例二:违反 T: 'static 泛型约束

这个例子模拟了多线程开发中最常见的生命周期错误:向新线程传递了一个包含局部变量引用的结构体。

1. 错误代码

use std::thread;

// 定义一个普通的结构体,内部包含一个带有生命周期 'a 的引用
struct RefWrapper<'a> {
    inner_ref: &'a String,
}

// 模拟一个需要 'static 约束的函数(类似 thread::spawn 的内部约束)
fn requires_static_type<T: 'static>(_data: T) {
    // 这里的 T 必须有能力活得和程序一样久
}

fn main() {
    let local_string = String::from("我是局部变量,存在于 main 的栈帧中");

    // 构造一个包装器,借用了 local_string
    let wrapper = RefWrapper {
        inner_ref: &local_string,
    };

    // 尝试传入要求 'static 约束的函数
    requires_static_type(wrapper); 
}

2. 编译器报错信息(核心部分)

error[E0310]: the parameter type `RefWrapper<'a>` may not live long enough
  --> src/main.rs:21:5
   |
21 |     requires_static_type(wrapper);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `RefWrapper<'a>` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
6  | struct RefWrapper<'a: 'static> {
   |                     +++++++++

3. 报错深度解析

  • RefWrapper<'a> may not live long enough:编译器警告 RefWrapper 活得不够久。
  • 类型生命周期不达标:requires_static_type 泛型函数要求传入的类型必须满足 T: 'static。然而,RefWrapper<'a> 的“生存能力”受限于它内部那个短命的引用 inner_ref(它的生命周期是 'a,只要 main 函数快结束了,local_string 被销毁,这个引用就失效了)。
  • 如何修正:编译器给出了一个几乎不可能实现的建议 'a: 'static(即让传入的字符串引用永久常驻内存)。在实际工程中,正确的修正方法是消除引用,直接传递拥有所有权的数据(比如直接传 String,或者利用 Arc 共享所有权),从而彻底让类型摆脱 'a 的束缚,达到 T: 'static 的要求。

通过这两个报错,你可以清晰地看到 Rust 编译器是如何在编译期利用 Sync 自动特质和 'static 泛型约束,把潜在的内存安全漏洞(多线程数据竞争、跨线程悬空指针)彻底扼杀在摇篮里的。

参考资料:

1.rust语言基础