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

推荐订阅源

N
News and Events Feed by Topic
A
About on SuperTechFans
D
DataBreaches.Net
量子位
云风的 BLOG
云风的 BLOG
T
The Blog of Author Tim Ferriss
U
Unit 42
M
MIT News - Artificial intelligence
小众软件
小众软件
博客园 - Franky
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Help Net Security
Help Net Security
Recorded Future
Recorded Future
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
S
Security @ Cisco Blogs
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
IT之家
IT之家
L
LINUX DO - 热门话题
H
Hacker News: Front Page
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 叶小钗
J
Java Code Geeks
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
PCI Perspectives
PCI Perspectives
Cyberwarzone
Cyberwarzone
S
Schneier on Security
Scott Helme
Scott Helme
Microsoft Security Blog
Microsoft Security Blog
Attack and Defense Labs
Attack and Defense Labs
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tenable Blog
Google DeepMind News
Google DeepMind News
T
Threat Research - Cisco Blogs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
V2EX
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Know Your Adversary
Know Your Adversary

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— VictoriaLogs 协同:Metrics 到 Logs 的一体化监控 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 源码阅读路线图:如何高效阅读 VM 源码 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开源生态:VM 在 CNCF 生态中的位置 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战 DeepSeek-R1 评估与系统(Evaluation & Systems)【左扬精讲】—— 从 GSM8K/MMLU 到 LLM-as-Judge 的工业级评估方法论 DeepSeek-R1 模型训练与算法【左扬精讲】—— GRPO 进阶算法:DAPO / PRIME / RLVR / PRM 四大 2025 前沿改进 DeepSeek-R1 模型训练与算法【左扬精讲】—— 数据蒸馏:用 DeepSeek-R1-671B 生成 800K 高质量 CoT 样本的完整流水线 DeepSeek-R1 优化与微调实战【左扬精讲】—— 从 R1 强化学习新范式到 GRPO 微调一站式入门 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 七):自定义插件开发实战 —— 手写一个 Score 插件并注册到集群 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 六):Scheduler Profile 与多调度器 —— 如何配置多个 profile 实现多租户、Coordinated LeaderElection Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 五):SchedulingQueue 与 QueueingHint —— 三段队列的细节、v1.36 新引入的 QueueingHint 工作机制 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 四):抢占(Preemption)算法剖析 —— DefaultPreemption 如何选 victim、PodDisruptionBudget 如何约束 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 二):内置插件逐个精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件 逐个精读 k8s 源码级精讲(二十六):调度器内置插件逐个精读 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):调度器内置插件精读 — NodeResourcesFit / NodeAffinity / TaintToleration / PodTopologySpread / VolumeBinding / InterPodAffinity Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):Scheduling Framework 扩展点逐个源码拆解 Kubernetes 源码 / Operator 专题【左扬精讲】——kube-scheduler(调度专题):初识调度模型、内部架构与事件驱动机制 Kubernetes 编程 / client-go 专题【左扬精讲】—— 四种客户端:为什么、怎么选、怎么用 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime、kubebuilder、operator-sdk 三大框架深度对比 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入理解 ManagedFields 字段冲突协调机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Finalizers 深度解析:对象的生命周期与删除控制 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference 字段与级联删除机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— 深入学习 Server-Side Apply:managedFields 替代 last-applied-configuration 的演进方向 Kubernetes 编程 / Operator 专题【左扬精讲】—— k8s Annotations 与元数据体系(Operator 专题) Kubernetes 编程 / Operator 专题【左扬精讲】—— RESTMapper:把 Group / Version / Kind / Resource 四元组翻译成 REST 路径的"查字典"大师 Kubernetes 编程 / Operator 专题【左扬精讲】—— Converter 资源版本转换器 Kubernetes 编程 / Operator 专题【左扬精讲】—— Application 业务扩展:从单 Deployment 到多 Workload 的复合 Operator 演进 Kubernetes 编程 / Operator 专题【左扬精讲】—— OwnerReference / Finalizer / 准入控制:k8s 资源生命周期的三大支柱 Kubernetes 编程 / Operator 专题【左扬精讲】—— controller-runtime 框架内幕:从 Manager 到 Reconcile 的全栈拆解 Kubernetes 编程 / Operator 专题【左扬精讲】—— 生产级 Operator 最佳实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— application-operator Reconcile 循环源码精讲:从 client-go Informer 到 workqueue 的全链路解剖 Kubernetes 编程 / Operator 专题【左扬精讲】—— 从零搭建一个 application-operator 新项目:脚手架、API 设计与基于原生 DeploymentStatus/ServiceStatus 的状态建模 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:生产级 Controller 实践:并发安全、资源清理与高可用设计 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析: Controller 调试与诊断工具:从日志分析到问题定位 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DynamicClient 操作 CRD:无需代码生成的动态操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:控制器与 APIServer 完整交互流程:从 Watch 到缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:错误处理与重试机制:WorkQueue 限速器详解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Leader 选举机制:高可用控制器的必备技能 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Controller 开发模式完整实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:SharedInformerFactory 与等待缓存同步 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:从认证配置到 Deployment 操作 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:版本对应、架构组件与组件关系 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Informer 源码深度解析:从底层原理到实战应用 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Reflector 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:ListWatcher 源码深度解析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:Indexer 与 ThreadSafeStore 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:DeltaFIFO 核心原理与源码深度剖析 Kubernetes 编程 / Operator 专题【左扬精讲】—— Client-go 源代码分析:workqueue 核心原理与实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— runtime.Codec 资源编解码:serializer 与 codec 差异、编解码数据结构、codec 核心调用链路 Kubernetes 编程 / Operator 专题【左扬精讲】—— Scheme 资源注册机制全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 自定义资源的内部版本与外部版本:从源码看版本定义机制 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 1.36.1 核心 API 数据结构全解 Kubernetes 编程 / Operator 专题【左扬精讲】—— Kubernetes 构建过程 【AIOPS】一文读懂LLM【左扬精讲】:从诞生到普及,解锁大语言模型的核心密码 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Hooks 源码精讲:Hooks 可观测性的无侵入式实现 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 配置解析源码精讲 ——SRE 自定义 Agent 核心技巧 【AIOPS】AI Agent 专题【左扬精讲】核心功能篇:MCP-VictoriaMetrics Golang 并发模型解析 ——SRE 应对高并发采集的调优思路 【AIOPS】AI Agent 专题【左扬精讲】基础架构篇:MCP-VictoriaMetrics Golang 源码整体架构拆解 ——SRE 必懂的核心模块与数据流 OpenTelemetry 开发实战【左扬精讲】—— 云原生可观测体系构建与分布式追踪二次开发 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(下篇)—— 预测式弹性扩缩容 Operator 落地实现) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 7 —— 基于流量预测模型的智能弹性扩缩容 Operator 实战(AIOps 模型训练与智能扩容(上篇)—— 时序预测模型构建与离线训练) Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 6 —— 基于运维专家知识库的智能故障诊断与排查 Operator 实战 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 5 —— 基于大语言模型(LLM)的实时日志流智能监测 Operator 实现 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 4 —— 基于 Operator 实现大模型私有化部署与管理 Kubernetes 编程 / Operator 专题【左扬精讲】—— Operator 开发实战项目 3(上篇)—— 面向 AI / 算力调度场景:GPU 竞价实例资源池统一调度管理 Operator 开发 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目 2 —— 面向零售 / 电商潮汐流量难题:多云多集群数据中心级全链路弹性伸缩 DataCenter Scaler Operator 从 0 到 1 全链路开发 Kubernetes编程 / Operator专题【左扬精讲】—— 深入理解Kubebuilder注解:为什么Operator开发离不开这些特殊注释 Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发实战项目1 —— Applicaion Operator(通用应用生命周期管理 Operator 实战) Pod 镜像拉取失败?kubectl edit pods修改镜像地址的底层原理与实操 (该方法仅为临时应急方案,并非长期解决方案) Kubernetes编程/Operator专题精讲—— 理解控制器模式 —— 控制器模式的核心原理与实现逻辑(从原理到实践) 【AIOPS】AI Agent 专题【左扬精讲】模型微调实战:一站式平台 LLaMA-Factory 【AIOPS】AI Agent 专题【左扬精讲】基于 k8s+vLLM+Ray 分布式部署全指南:架构设计、资源调度与性能优化 【AIOPS】AI Agent专题【左扬精讲】非量化版DeepSeek分布式部署全指南:精度保障、显存规划与Ollama/vLLM选型 【AIOPS】AI Agent 专题【左扬精讲】零开发框架实现 ReAct Agent(Go SRE友好)
Rust 专题【左扬精讲】—— 所有权详解
左扬 · 2026-06-24 · via 博客园 - 左扬

Rust 专题【左扬精讲】—— 所有权详解

Rust 所有权 Move 语义 借用规则 Copy / Clone 多语言对比

学习重点提示建议先通读全文,再重点回顾标注内容

重点掌握(必须)

  • 所有权三规则:每个值有唯一所有者、值随所有者超出作用域而 drop、所有权可转移(Move)
  • Move 语义:堆上数据的所有权转移,而非浅拷贝;转移后原变量不可用
  • Copy trait:栈上数据自动按位复制,无所有权转移;Clone 是显式深拷贝
  • 借用规则:同一时刻可变借用或多个不可变借用,编译期消除数据竞争

次重点(理解即可)

  • 智能指针 Box<T>Rc<T>Arc<T> 与所有权的交互
  • 所有权与其他语言 GC/手动管理的本质区别
  • Unsafe Rust 中对所有权规则的绕过

目录


FAQ · 临考前速背(20 组)

思考记忆提示FAQ 是全篇的"临考前速背"模块,建议在通读全文后作为复习使用

  • Q1-Q7 围绕所有权基础:三规则、Move 语义、Copy vs Clone、所有权转移
  • Q8-Q14 围绕借用与所有权的交互:借用规则、生命周期、函数参数、返回值
  • Q15-Q20 围绕高级话题与多语言对比:Rc/Arc、unsafe、GC vs 所有权系统

Q1. Rust 的所有权(Ownership)是什么?

所有权是 Rust 用来管理堆内存的一套规则体系——每个值有且仅有一个"所有者"(Owner),所有者负责在值超出作用域时调用 Drop 释放资源。这使得 Rust 不需要垃圾回收器(GC)就能自动管理内存:所有权的转移(Move)、复制(Copy)和释放(Drop)都在编译期确定,没有运行时开销。简单说,所有权 = "谁负责清理这块内存"。

Q2. 所有权的三条核心规则是什么?

规则一:Rust 中的每个值都有一个所有者(Owner);规则二:同一时刻每个值只有一个所有者;规则三:当所有者超出作用域(scope)时,值将被删除(drop)。这三条规则完全由编译器强制执行,违反任何一条都会导致编译错误。这三条规则是 Rust 内存安全体系的基石——第一条定义了"谁来负责",第二条定义了"唯一性保证",第三条定义了"何时清理"。

Q3. 什么是 Move 语义?为什么 String 在赋值时会"移动"而不是复制?

Move 语义是指将值的所有权从源变量转移给目标变量,转移后源变量失效,堆内存不发生复制。对于 String 这样的堆上数据,赋值操作 let s2 = s1; 会将 s1 持有的堆上数据指针的所有权转移给 s2,s1 的内部指针被设为空,s2 成为唯一持有者。当 s1 和 s2 超出作用域时,不会出现"双重释放"(double free)——因为 s1 已经不再拥有那块内存。

let s1 = String::from("hello"); // s1 拥有堆数据的所有权
let s2 = s1;                     // Move: s1 将所有权转移给 s2
// println!("{}", s1);           // error: s1 的所有权已经转移,不再有效
println!("{}", s2);               // OK: s2 是新的所有者

Q4. Copy trait 和 Clone trait 有什么区别?

Copy 是隐式的、按位复制的、无额外操作的;Clone 是显式的、可以任意复杂复制的、需要手动调用。实现了 Copy trait 的类型(如 i32f64bool)在赋值时自动按位复制,原变量仍然有效——因为这些类型足够简单,复制成本可忽略不计且不存在资源管理问题。而 CloneCopy 的超集(Clone: Copy),任何能 Copy 的类型也能 Clone,但反之不一定。

// Copy:隐式,零成本
let a: i32 = 42;
let b = a;        // Copy: a 和 b 各持有一个独立的 i32 值
println!("{} {}", a, b); // OK: 两个都有效

// Clone:显式,可能有成本
let s1 = String::from("hello");
let s2 = s1.clone(); // Clone: 深拷贝,s1 和 s2 都有效
println!("{} {}", s1, s2); // OK: "hello hello"

Q5. 为什么 Rust 不让所有类型都自动实现 Copy?

因为对于拥有堆上资源(如堆内存、文件句柄、网络连接)的类型,自动按位复制会导致多个所有者指向同一资源,从而引发双重释放或数据竞争。编译器通过 Copy trait 的设计来强制开发者对"昂贵复制"和"资源管理"做出显式决策——如果一个类型持有堆数据,它就不能是 Copy(因为 Move 比 Copy 更安全);如果想复制,必须调用 .clone(),这是开发者的有意识决策。

Q6. 函数参数和返回值会转移所有权吗?

会。当把值作为参数传入函数时,所有权会转移给函数参数(如果类型是 Move 类型);函数也可以将所有权通过返回值转移回来。这是 Rust 最优雅的设计之一——函数通过参数"接收"所有权,通过返回值"归还"所有权。如果不想转移所有权,可以借用(&)或使用智能指针(Rc/Arc)。

fn take_ownership(s: String) { // s 获得字符串的所有权
    println!("{}", s);
} // s 超出作用域,字符串被 drop

fn main() {
    let s = String::from("hello");
    take_ownership(s);          // s 的所有权转移到函数参数
    // println!("{}", s);       // error: s 已经不再拥有这个字符串
}

fn restore_ownership() -> String { // 通过返回值恢复所有权
    let s = String::from("world");
    s // s 的所有权转移给调用者
}

Q7. 结构体的所有权规则是什么?为什么包含堆数据的结构体默认不是 Copy?

结构体的所有权取决于它的字段——只要有一个字段不是 Copy,整个结构体就不能是 Copy。这是 Rust 的"零成本抽象"原则的体现:如果结构体包含 StringVec<T>Box<T> 等堆上数据类型,这些字段拥有资源管理职责,无法按位复制,因此结构体本身也不能是 Copy。赋值给新变量时,整个结构体会 Move(所有权转移)。

struct Person {
    name: String,  // 堆数据,不是 Copy
    age: i32,      // 栈数据,是 Copy
}

let p1 = Person { name: String::from("Alice"), age: 30 };
let p2 = p1;            // Move: p1 的所有权转移给 p2
// println!("{}", p1.name); // error: p1 不再拥有这个 Person

// 如果希望 name 也能复制,需要 Clone:
let p3 = Person {
    name: p1.name.clone(), // 深拷贝
    age: p1.age,            // Copy: i32 自动复制
};
println!("{} {}", p3.name, p3.age);

Q8. 借用(Borrowing)和所有权转移(Move)有什么区别?

Move 转移了值的所有权,借用(&)只是临时"借阅"值的所有权,原所有者不变。Move 后原变量失效,后续无法使用;借用后原变量仍然有效,借用期结束后自动归还。借用本质上是在不转移所有权的前提下访问数据,是 Rust 中最常用的模式——它让函数可以"看"数据而不"拿走"数据。

let s1 = String::from("hello");

// Move: s1 失效
let s2 = s1;
// s1 不再可用

// Borrow: s1 仍然有效
let s3 = &s1;
println!("{} {}", s1, s3); // s1 和 s3 都有效

Q9. 什么是可变借用(&mut T)?它和不可变借用的区别是什么?

可变借用(&mut T)允许修改借来的数据,但同一时刻只能存在一个可变借用(不能和其他任何借用共存);不可变借用(&T)只能读取数据,但可以同时存在多个。这是 Rust"独占访问"(Exclusive Access)原则的具体体现——要么多个人一起读书(多个不可变借用),要么一个人单独编辑(一个可变借用),不能同时有编辑者又有阅读者。这条规则由借用检查器在编译期强制执行。

let mut v = vec![1, 2, 3];
let r1 = &v;          // 不可变借用:OK
let r2 = &v;         // 多个不可变借用:OK
println!("{:?}", r1);

// 可变借用:在不可变借用之后创建可变借用(NLL 之后合法)
let rm = &mut v;
rm.push(4);
// rm 在这里不再使用
let r3 = &v;         // OK: rm 已经结束
println!("{:?}", r3);

Q10. Rust 的所有权系统和垃圾回收器(GC)有什么本质区别?

所有权系统在编译期确定何时释放内存,无运行时开销且行为可预测;GC 在运行时追踪引用并异步回收内存,有额外开销且释放时机不确定。GC 语言(Java、Go、Python)通过追踪"还有哪些引用指向这块内存"来判断是否需要保留这块内存。Rust 则通过"谁拥有这块内存"来判断——所有者超出作用域时立即回收,没有 GC 的"Stop the World"暂停,没有引用计数(除非显式使用 Rc/Arc)。所有权系统让 Rust 获得了 C/C++ 的性能,同时获得了内存安全的保证。

Q11. 什么是双重释放(Double Free)?Rust 如何防止它?

双重释放是指同一块内存被释放两次,导致堆元数据损坏或安全问题,是 C/C++ 中最常见的安全漏洞之一。Rust 通过 Move 语义防止双重释放:当 String 被赋值给另一个变量时,原变量的内部指针被设为空(null),原变量不再持有任何堆内存的所有权。当两个变量各自持有不同的堆内存时,各自析构时释放各自的内存——不会冲突。

// C++ 中的双重释放(危险):
// char* s1 = new char[6]; strcpy(s1, "hello");
// char* s2 = s1; // 浅拷贝,两个指针指向同一块内存
// delete[] s1; delete[] s2; // 双重释放!UB!

// Rust 中不会有这个问题:
let s1 = String::from("hello");
let s2 = s1; // Move: s1 的所有权转移,s1 的内部指针被清空
// Rust 保证同一时刻只有一个所有者
// s1 和 s2 各自持有不同的内存(或 s1 完全失效)

Q12. Rust 的 Rc<T>(引用计数)是如何在不违反所有权规则的前提下工作的?

Rc<T> 通过"共享所有权"(Shared Ownership)的概念绕过单一所有者限制——堆数据的所有权归 Rc 包装器持有,多个变量可以持有指向同一个 Rc 的引用,引用计数跟踪活跃的所有者数量,最后一个 Rc 超出作用域时才 drop 数据。Rc<T> 是单线程的(不提供原子性);对于多线程场景,需要使用 Arc<T>(Atomic Rc),它的引用计数操作是原子的,线程安全的。

use std::rc::Rc;

let s1 = Rc::new(String::from("hello"));
let s2 = Rc::clone(&s1); // 共享所有权,引用计数 +1
println!("引用计数: {}", Rc::strong_count(&s1)); // 2
{
    let s3 = Rc::clone(&s1);
    println!("引用计数: {}", Rc::strong_count(&s1)); // 3
} // s3 超出作用域,引用计数 -1
println!("引用计数: {}", Rc::strong_count(&s1)); // 2

Q13. 为什么 Rc<T> 不能有可变借用(&mut T)?

Rc<T> 内部使用非原子引用计数(Rc),多个克隆共享同一个计数,如果允许 &mut T 可变借用,就可能在多线程场景中产生数据竞争——这与 Rust 的安全目标冲突。对于需要内部可变的共享所有权场景,Rust 提供了 RefCell<T>(单线程)和 Mutex<T>/RwLock<T>(多线程)作为解决方案——它们在运行时执行借用规则检查,而不是编译期。

Q14. 所有权和借用规则在多线程场景中如何保证数据竞争(Data Race)安全?

Rust 的借用规则和所有权系统从语言层面保证了数据竞争的安全——同一时刻只有一个可变引用(独占写)或多个不可变引用(并发读),两者不能同时存在。在单线程中这是编译期检查;在多线程中,Arc<T>(原子引用计数)配合 Mutex<T>(互斥锁)或 RwLock<T>(读写锁)实现线程安全。SendSync trait 是 Rust 标记类型是否可以在线程间传递的机制——编译器自动推断大多数类型的这两个 trait,进一步减少数据竞争风险。

Q15. 如果我想在 Rust 中共享可修改的状态,应该用哪些组合?

单线程场景用 Rc<RefCell<T>>(引用计数 + 内部可变性),多线程场景用 Arc<Mutex<T>>(原子引用计数 + 互斥锁)或 Arc<RwLock<T>>(读写锁)。这两组组合分别被称为"内部可变性模式"——它们在不违反所有权规则的前提下,提供了共享可修改状态的能力。Rc<RefCell<T>> 是编译期安全检查的"运行时补偿",Arc<Mutex<T>> 是多线程版的等效方案。

Q16. Rust 的所有权规则和 C++ 的 RAII 有什么区别?

C++ 的 RAII 只负责"在析构时释放资源",但允许指针自由复制(浅拷贝),没有所有权的概念;Rust 的所有权系统在 RAII 基础上增加了"单一所有者 + Move 语义 + 借用检查",彻底消灭了悬垂指针和野指针。在 C++ 中,std::unique_ptr 最接近 Rust 的单一所有者模型,但 C++ 允许绕过 unique_ptr 直接操作裸指针,而 Rust 不允许——裸指针操作必须在 unsafe 块中进行,且 unsafe 块不会改变借用规则。

Q17. Go 的 GC 和 Rust 的所有权系统在性能和安全性上各有什么优缺点?

Go 的 GC 提供便捷的内存管理,但有"Stop the World"暂停风险和额外的运行时开销;Rust 的所有权系统在编译期完成内存管理,性能与 C/C++ 持平,但要求开发者显式处理所有权的转移和借用。Go 的 defer 提供了类似 RAII 的资源清理能力,但 defer 不能处理堆内存(交给 GC),而 Rust 的 Drop 自动处理所有实现了 Drop 的类型(堆内存、文件句柄、网络连接、锁等)。

Q18. Rust 的 Box<T>(堆分配)是如何参与所有权系统的?

Box<T> 将数据强制分配到堆上,并将这个堆数据的所有权包装为一个值——赋值时 Move 整个 Box,即转移堆数据指针的所有权,而非复制堆数据。Box<T> 实现了 DerefDrop,让堆数据可以像栈数据一样用 * 解引用,并在超出作用域时自动 drop。它是 Rust 中"将堆数据作为值使用"的标准方式,与 C++ 的 std::unique_ptr 类似。

Q19. 在 Rust 中,什么时候应该用所有权转移(Move),什么时候应该用借用(&)?

当函数需要"消耗"(consume)数据时用 Move(如 Stringinto_bytes());当函数只需要"读取"或"临时修改"数据时用借用(&&mut)。经验法则:优先用不可变借用(&T),因为它最灵活(允许多个同时存在);只有需要修改数据时才用可变借用(&mut T);只有需要转移所有权时才用 Move。Rust 的类型系统通过 Deref trait 让 Box<T> 的借用和解引用操作完全透明。

Q20. Rust 的所有权系统对系统编程(如嵌入式、操作系统内核)有什么特殊意义?

Rust 的所有权系统在无运行时(No Stdlib / No Alloc)的嵌入式环境中完全可用——不需要 GC、不需要堆分配器,只需要编译器。这使得 Rust 成为第一个能系统编程(OS 内核、驱动、嵌入式固件)而不损失内存安全保证的语言。在 no_std 环境中,Rust 的所有权系统仍然有效,所有权检查在编译期完成,不需要任何运行时支持——这是 Rust 相比所有 GC 语言和大多数手动管理语言的独特优势。

全篇必记总纲

所有权是 Rust 内存安全体系的灵魂:单一所有者确保"谁负责清理",Move 语义确保"清理不会重复",借用规则确保"读写访问有秩序",Drop trait 确保"退出作用域即释放"——四条规则协同,在编译期彻底消灭了 GC 时代才能防住的内存安全问题,同时实现了与 C/C++ 相当的运行时性能。


Roadmap · 学习路线图

所有权是 Rust 最核心的概念,是理解借用、生命周期和所有智能指针的基础。建议按以下顺序学习:

What 是什么 三规则、Move / Copy / Clone、借用与所有权的关系

Why 为什么 消灭 double free、悬垂指针、数据竞争的性能与安全收益

How 怎么做到的 Drop trait、借用检查器、生命周期、NLL 的实现机制


一、What · 什么是所有权

1.1 所有权的定义

所有权(Ownership)是 Rust 独创的一套内存管理模型。在 Rust 中,每个值(value)都有一个"所有者"(owner),所有者是一个变量。当这个变量超出作用域时,Rust 会自动调用该值的 Drop trait,释放它所占用的资源——堆内存、文件句柄、网络连接等。这套机制完全在编译期实现,没有垃圾回收器的运行时开销。

三个核心术语

  • Owner(所有者):持有值的所有权的变量,职责是"在值不再需要时释放它"
  • Move(移动):将所有权从一个变量转移给另一个变量,原变量失效
  • Drop(析构):当所有者超出作用域时,Rust 自动调用的清理函数

1.2 所有权的三条规则

Rust 官方文档明确列出了所有权的三条规则,所有 Rust 编译器强制执行这三条规则:

// Rust 所有权三条规则(编译期强制执行)
// 规则一:每个值有一个所有者(每个值只能有一个变量"负责"它)
// 规则二:同一时刻每个值只有一个所有者(不能两个人同时"负责")
// 规则三:所有者超出作用域时,值被删除(内存自动释放)

fn main() {
    // 规则一演示:s 是字符串的所有者
    let s = String::from("hello");

    // 规则二演示:Move 之后,s2 是新的唯一所有者
    let s2 = s; // Move: s 将所有权转移给 s2

    // 规则三演示:s2 超出作用域时,字符串被自动 drop
}
// main 函数结束时,所有本地变量按 FILO 顺序析构

1.3 Move 语义详解

Move 是 Rust 中最核心的语义之一。对于拥有堆上数据的类型(如 StringVec<T>Box<T>),赋值操作会触发 Move——原变量的内部指针被"移出",变为空(逻辑上相当于被设为 None),新变量继承堆数据的所有权。

1.3.1 哪些类型会 Move?

所有没有实现 Copy trait的类型,在赋值、函数传参、函数返回值时都会 Move:

// 会 Move 的类型(堆数据或复杂资源管理)
String, Vec<T>, Box<T>, HashMap<K, V>, TcpStream, File, Mutex<T>, ...

// 不会 Move 的类型(实现了 Copy,在栈上按位复制)
i32, f64, bool, char, (i32, i32), [u8; 4], 所有固定大小的元组和数组

1.3.2 Move 之后原变量发生了什么?

let s1 = String::from("hello");
// 内部:s1 是一个结构体 { ptr: 0x1234, len: 5, capacity: 5 }
// 堆地址 0x1234 上存储着 "hello" 这 5 个字节

let s2 = s1;
// Move: s1 的 ptr 被"移出",s2 的 ptr = 0x1234(同一个堆地址)
// s1 的 ptr 被设为 0x0(空指针),s1 不再拥有堆内存
// s1 仍然是有效的变量,但它的值是"空 String"(ptr=null, len=0)

// println!("{}", s1); // error: s1 的字符串已经被移走了

设计精髓:Rust 为什么用 Move 而不是像 C++ 那样默认浅拷贝?

Rust 认为"隐式复制你不知道大小的数据"是危险的——C++ 的默认拷贝对于 std::vector 这样的类型会导致高昂的深拷贝成本,开发者可能无意间写出 O(n) 的赋值操作。Rust 默认 Move,让开发者明确意识到"这个数据的所有权被转移了",如果真的需要复制,显式调用 .clone()——这是 Rust"零成本抽象"和"显式优于隐式"哲学的体现。

1.4 Copy 与 Clone

1.4.1 Copy trait

Copy 是 Rust 中一个特殊的 trait,标记"可以在赋值时按位复制的类型"。实现了 Copy 的类型不涉及堆内存管理,不会有双重释放风险——所以编译器允许它们在赋值时自动复制。

// Copy 类型示例:所有字段都是 Copy 的结构体
#[derive(Debug, Clone, Copy)]
struct Point { x: i32, y: i32 }

let p1 = Point { x: 1, y: 2 };
let p2 = p1;        // Copy: p1 和 p2 各持有一个独立的 Point
println!("{:?}", p1); // OK: p1 仍然有效

1.4.2 Clone trait

Clone 是显式深拷贝,可以对任意类型进行完整的复制(如果是堆数据,则复制堆上的内容)。调用 .clone() 是开发者的有意识决策。

let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝:堆上 "hello" 被复制一份
println!("{} {}", s1, s2); // 两个都有效

1.4.3 Copy 与 Clone 的关系

// Clone 是 Copy 的超集:能 Copy 的类型必定能 Clone
// 但反过来不一定成立(Clone 可能很贵,Copy 应该是零成本)
trait Copy: Clone { } // Copy 依赖 Clone,语义上 Copy 是"隐式 Clone"

// 标准库实现规范:
// Copy = "按位复制,零成本,无额外逻辑"
// Clone = "完整复制,可能有成本,需要显式调用"

1.4.4 标准库中的 Copy 类型一览

类型是否 Copy说明
i32, i64, u32, f32, f64 Copy 固定大小的基本数值类型
bool, char Copy 固定大小的单值类型
[T; N](固定大小数组) Copy(当 T 是 Copy 时) 编译期已知大小的数组
(T, U)(元组) Copy(当 T 和 U 都是 Copy 时) 元组字段递归判断
String Move(非 Copy) 堆上数据,所有权转移
Vec<T> Move(非 Copy) 堆上数据,所有权转移
Box<T> Move(非 Copy) 堆指针,所有权转移

1.5 函数与所有权

函数参数接收所有权,返回值交还所有权——这是 Rust 函数式编程风格的核心。

fn process(s: String) -> String {
    // s 拥有字符串的所有权
    let transformed = format!("processed: {}", s);
    transformed // 所有权转移给调用者
}

fn main() {
    let original = String::from("data");
    let result = process(original); // original 的所有权转移给 process 的参数 s
    // original 在这里不再可用
    println!("{}", result); // OK: result 持有字符串所有权
}

思考记忆:所有权的"入"与"出"

  • 函数参数是"单向流入"——调用者失去所有权,函数参数获得所有权
  • 返回值是"单向流出"——函数失去所有权,返回值携带所有权给调用者
  • 如果既不想转移也不想复制,可以借用(&)——所有权不转移,函数只是"借用"访问权

二、Why · 为什么要所有权

2.1 解决的问题一:双重释放(Double Free)

在 C/C++ 中,当两个指针指向同一块堆内存,手动释放两次会导致堆元数据损坏,引发崩溃或安全漏洞。Rust 的 Move 语义彻底消除了这个问题——赋值操作转移所有权,而不是复制指针。

// C 语言:double free 危险
char* s1 = (char*)malloc(6);
strcpy(s1, "hello");
char* s2 = s1; // 浅拷贝:s1 和 s2 指向同一块内存
free(s1);       // 第一次释放:内存归还系统
free(s2);       // 第二次释放:危险!未定义行为,可能被利用为安全漏洞
// Rust:Move 语义天然防止双重释放
let s1 = String::from("hello");
let s2 = s1; // Move: s1 的所有权转移给 s2,s1 的内部指针被清空
// Rust 保证:同一时刻只有一个所有者
// 当 s1 和 s2 超出作用域时,各自只释放自己持有的数据

2.2 解决的问题二:悬垂指针(Dangling Pointer)

在 C/C++ 中,返回函数内局部变量的指针会导致悬垂指针(指向已释放的栈帧)。Rust 的生命周期系统确保返回引用时,引用不能指向局部变量(局部变量在函数退出时被 drop,返回引用将成为悬垂引用)。

// C 语言:返回栈变量的地址(危险)
int* create_int() {
    int x = 42;
    return &x; // 危险!x 在函数返回时被弹出栈,返回的指针悬垂
}
// Rust:生命周期注解保证引用不指向已销毁的数据
// 错误:编译期直接拒绝
fn dangle() -> &String { // error: missing lifetime specifier
    let s = String::from("hello");
    &s // s 在函数末尾被 drop,返回的引用悬垂!
}
// 正确:返回所有权而非引用
fn no_dangle() -> String {
    let s = String::from("hello");
    s // s 的所有权被转移给调用者,s 不会被 drop
}

2.3 解决的问题三:数据竞争(Data Race)

数据竞争发生在多个线程同时读写同一块内存时,且至少有一个是写操作。Rust 的借用规则(同一时刻:多个不可变引用 OR 一个可变引用)从语言层面消除了数据竞争。

use std::thread;
use std::sync::Arc;
use std::sync::Mutex;

fn main() {
    // Arc<T>: 原子引用计数,允许多线程共享所有权
    // Mutex<T>: 互斥锁,保证同一时刻只有一个线程能修改数据
    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()); // 总是 10
}

注意:Arc<Mutex<T>> 的工作原理

Arc(原子引用计数)允许多个线程持有同一个值的所有权副本,引用计数保证最后一个持有者负责 drop。Mutex(互斥锁)包装内部数据,只有成功获取锁的线程才能修改数据——这正是 Rust 用编译期检查(Send/Sync)+ 运行时锁实现的"安全并发"。

2.4 解决的问题四:迭代器失效(Iterator Invalidation)

在 C++ 中,遍历 vector 时如果修改了 vector,迭代器会失效(iterator invalidation)。Rust 的借用规则保证:修改 vector 期间不能持有 vector 的迭代器,编译期就阻止了这类错误。

let mut v = vec![1, 2, 3];
for val in &v {          // 不可变借用开始
    // v.push(4);          // error: 无法在持有不可变借用时创建可变借用
    println!("{}", val);
} // 不可变借用结束

v.push(4); // OK: 迭代器已经结束,现在可以修改

2.5 Rust 所有权 vs 其他语言内存管理方案

语言内存管理方案双重释放防护悬垂指针防护数据竞争防护性能开销
Rust 所有权系统(编译期分析) Move 语义,编译期保证 生命周期注解,编译期保证 借用规则,编译期保证 零运行时开销
C 手动(malloc/free) 无(程序员责任) 最低
C++ RAII + 智能指针(手动选择) unique_ptr 自动防护 无(裸指针无防护) 无(需 std::mutex 接近零
Python GC(引用计数 + 标记清除) GC 保护 GC 保护 GIL 限制 GC 暂停开销
Go GC(并发三色标记) GC 保护 GC 保护 channel 约定(无编译期保证) GC 暂停开销
Java GC(分代收集) GC 保护 GC 保护(无指针) 无(需 synchronized GC 暂停开销
JS GC(标记清除) GC 保护 GC 保护(无指针) Event Loop 单线程 GC 暂停开销

对比记忆口诀

  • C/C++:最灵活,也最危险——手动管理,没有安全网
  • GC 语言(Python/Go/Java/JS):最安全,但有代价——运行时开销,不确定性暂停
  • Rust:编译期安全 + 零运行时开销——第一次就把事情做对(Make Wrong Code Uncompileable)

三、How · 所有权的深层机制

3.1 Drop trait:RAII 的实现

当值超出作用域时,Rust 自动调用 Drop::drop(&mut self) 方法。这使得资源释放是确定性的——与 C++ RAII 完全相同,但 Rust 的 Drop 比 C++ 更安全(不需要手动管理)。

struct HasDrop {
    data: String,
}

impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("Dropping: {}", self.data);
    }
}

fn main() {
    let a = HasDrop { data: String::from("first") };
    {
        let b = HasDrop { data: String::from("second") };
        println!("内层块结束,b 将被 drop");
    } // b.drop() 被调用
    println!("a 仍然存活");
} // a.drop() 被调用(FILO 顺序)

Drop 的析构顺序:变量按照与构造顺序相反的顺序被 drop(FILO,First In Last Out)。栈上的局部变量从最后一个声明的变量开始析构。内层块的变量先于外层块被 drop。

3.2 借用检查器的工作机制

借用检查器(Borrow Checker)是 Rust 编译器(rustc)的一个组件,它在编译期追踪每个引用的活跃区间(Live Range),验证借用规则不被违反。

3.2.1 借用检查器追踪的三个核心约束

  • 约束一:同一作用域内,不能同时存在可变借用(&mut)和任何其他借用(& 或另一个 &mut
  • 约束二:可变借用是独占的——同一时刻只能有一个 &mut
  • 约束三:借用不能比它引用的数据的生命周期更长(见生命周期专题)

3.2.2 NLL(Non-Lexical Lifetimes)与借用作用域

Rust 2018 引入 NLL 后,借用的作用域不再是大括号边界,而是"最后一次使用"的位置。这大大减少了"假性冲突"——借用检查器的行为更符合开发者的直觉。

let mut v = vec![1, 2, 3];
let first = &v[0];       // 不可变借用:first 的借用作用域到 println! 为止
println!("{}", first);     // 最后使用 first
v.push(4);               // NLL: first 的借用已结束,可变借用 OK!
// Rust 2018 之前:first 的借用作用域到函数末尾,这里 push 会编译失败

3.3 所有权的转移路径:完整图解

// Rust 所有权转移的几种方式

// 方式一:赋值 Move
let s1 = String::from("hello");
let s2 = s1; // Move: s1 -> s2,s1 失效

// 方式二:函数参数 Move
fn consume(s: String) { } // 参数 s 获得所有权
consume(s2);              // s2 的所有权转移给 consume

// 方式三:函数返回值恢复所有权
fn produce() -> String { String::from("world") }
let s3 = produce();       // produce 返回值的所有权转移给 s3

// 方式四:Clone(显式深拷贝,不转移所有权)
let s4 = String::from("clone");
let s5 = s4.clone();     // s4 仍然有效,s5 是新的独立副本

// 方式五:Copy(栈上类型,不涉及 Move)
let a = 42;
let b = a;               // Copy: a 和 b 都有效,都是独立的 i32

3.4 智能指针与所有权

智能指针是实现了 DerefDrop 的类型,它们以"指针"的形式包装数据,同时承担资源管理的职责。

3.4.1 Box<T>:堆分配

// Box<T> 将数据分配到堆上,整个 Box 作为值在栈上传递
let b1 = Box::new(String::from("heap data"));
let b2 = b1; // Move: b1 的所有权转移给 b2,堆数据不复制
// b1 不再可用
println!("{}", b2); // OK

// Box<T> 实现了 Deref,可以像普通引用一样使用
fn print_box(s: &str) { println!("{}", s); }
print_box(&b2); // 自动解引用:&Box<String> -> &String -> &str

3.4.2 Rc<T>:单线程共享所有权

use std::rc::Rc;

let s1 = Rc::new(String::from("shared"));
let s2 = Rc::clone(&s1); // 引用计数 +1,s1 和 s2 共享所有权
let s3 = Rc::clone(&s1); // 引用计数 +1

println!("引用计数: {}", Rc::strong_count(&s1)); // 3

// Rc<T> 只能进行不可变借用(&T),不能可变借用(&mut T)
// 因为 Rc<T> 内部是非线程安全的引用计数
// 如果需要可变借用,用 Rc<RefCell<T>>

3.4.3 Arc<T>:多线程共享所有权

use std::sync::Arc;
use std::thread;

let data = Arc::new(vec![1, 2, 3]);

let data2 = Arc::clone(&data); // 原子引用计数 +1
let handle = thread::spawn(move || {
    println!("子线程: {:?}", data2);
});

println!("主线程: {:?}", data);
handle.join().unwrap();
// Arc 的引用计数在所有线程结束后减为 0,vec 被 drop

3.5 解构(Destructure)与所有权

Rust 支持用 let 解构来"拆分"一个值,同时转移或复制其字段的所有权:

let (x, y) = (String::from("hello"), 42);

// x 是 String,Move 语义:x 被绑定到第一个元素
// y 是 i32,Copy 语义:y 是第二个元素的按位复制

// 结构体解构
struct Point { x: f64, y: f64 }
let p = Point { x: 1.0, y: 2.0 };
let Point { x, y } = p; // x 和 y 各获得一个字段(x: Copy, y: Copy)
println!("{} {}", x, y);

四、多语言横向对比

4.1 C / C++ vs Rust:所有权 vs 手动管理

// C:手动内存管理 + 悬垂指针风险
char* create_string() {
    char* s = (char*)malloc(6);
    strcpy(s, "hello");
    return s; // OK(堆上分配),但忘记 free 就内存泄漏
}
// C++:RAII,但默认浅拷贝导致双重释放风险
std::string s1 = "hello";
std::string s2 = s1; // 默认拷贝构造函数,深拷贝(安全但昂贵)
// unique_ptr 的 move 语义最接近 Rust:
std::unique_ptr<std::string> p1 = std::make_unique<std::string>("hello");
std::unique_ptr<std::string> p2 = std::move(p1); // Move: p1 失效,p2 持有资源
// p1 变成空 unique_ptr,不会 delete 任何东西

C++ std::unique_ptr vs Rust 单一所有者

std::unique_ptr 是 C++11 引入的最接近 Rust 所有权的概念——两者都实现了"独占所有权 + Move 语义 + 自动析构"。但关键区别是:C++ 的 unique_ptr 可以通过 .release() 放弃所有权并返回裸指针(危险操作),而 Rust 的所有权转移是单向的、更安全的。

4.2 Python vs Rust:GC vs 所有权

# Python:所有东西都是引用,GC 自动处理
s1 = "hello"
s2 = s1  # 两个变量引用同一个字符串对象
s2 = "world"  # s2 现在引用新字符串,s1 仍然引用 "hello"
# GC 追踪引用计数,最后一个引用消失时触发析构
# 问题:循环引用(a -> b -> a)导致内存泄漏(Python 用标记清除处理)

class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

a = Node(1)
b = Node(2)
a.next = b  # a 持有 b
b.next = a  # b 持有 a —— 循环引用,引用计数无法回收
# Python 用分代 GC 标记清除来处理循环引用
// Rust:所有权系统 + 借用规则,循环引用被编译期拒绝
struct Node { val: i32, next: Option<Box<Node>> }

fn main() {
    let a = Box::new(Node { val: 1, next: None });
    let b = Box::new(Node { val: 2, next: None });
    a.next = Some(b); // a 拥有 b
    // b.next = Some(a); // error: 不能在 a 已经拥有 b 时让 b 拥有 a(循环所有权)
    // 编译器检查:A 拥有 B 意味着 A 不能同时被 B 拥有(会形成循环)
}

Rust 如何处理需要循环引用的场景?

如果业务逻辑确实需要循环引用(如链表、树),Rust 提供了两种方案:Rc<T>(弱引用计数,允许循环)+ Weak<T>(弱引用,不参与引用计数)。Rc<T> 配合循环引用会引发内存泄漏(因为引用计数永远不会到 0),但这比 GC 语言中的循环引用更容易被检测和修复。

4.3 Go vs Rust:GC vs 所有权

// Go:GC 自动回收,无需所有权概念
func consume(s []byte) {
    // s 是切片(引用类型),底层指向数组
    // GC 追踪 s 引用的数组,函数结束后如果无其他引用则回收
}
func main() {
    s := []byte{'h','e','l','l','o'}
    consume(s)
    // s 仍然有效(Go 的切片是引用传递,不会 Move)
}
// Rust:所有权是显式的,借用必须显式声明
fn consume(s: &[u8]) { // 借用切片(不转移所有权)
    println!("{:?}", s);
}
fn main() {
    let s = vec![1, 2, 3, 4, 5];
    consume(&s); // 显式借用
    // s 仍然有效(所有权未转移)
}

4.4 Java / TypeScript vs Rust:引用 vs 所有权

// Java:一切皆引用(除基本类型),GC 管理生命周期
public class Demo {
    static void process(StringBuilder sb) {
        sb.append(" processed"); // 直接修改引用的对象
        // sb 超出作用域,但引用的堆对象不会被 GC 回收
        // (main 函数中还有引用)
    }
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("hello");
        process(sb);
        System.out.println(sb.toString()); // "hello processed"
    }
}
// Java 的引用是共享的,没有"独占所有权"的概念
// 多个变量可以指向同一个对象,没有任何编译期检查
// Rust:借用是唯一安全的共享方式,且有独占检查
fn process(sb: &mut String) {
    sb.push_str(" processed");
}
fn main() {
    let mut sb = String::from("hello");
    process(&mut sb); // 可变借用
    println!("{}", sb); // "hello processed"
    // Rust 的借用检查器保证同一时刻只有一个可变借用
    // 编译期就阻止了多线程数据竞争
}

我的理解:Rust 所有权 vs 其他语言的"共享"模型

Java/JS/Python 的"共享"模型是"任何地方都可以持有指向同一对象的引用"——方便但危险(无编译期检查)。Rust 的"共享"模型是"要么独占(Move/Box),要么借用(&/&mut),要么计数共享(Rc/Arc)"。三条路泾渭分明,每条路都有编译期安全保证。


五、总结

本专题核心要点

  • 三规则:每个值一个所有者、同一时刻单一所有者、所有者超出作用域即 drop
  • Move 语义:堆上数据赋值时所有权转移,原变量失效;Copy 类型(栈数据)赋值时按位复制
  • Copy vs Clone:Copy = 隐式零成本,Clone = 显式可能有成本的深拷贝
  • 借用规则:多个不可变借用 OR 一个可变借用,编译期消除数据竞争
  • Drop trait:RAII 资源管理,作用域末尾自动析构,无 GC 依赖
维度Rust 特色其他语言对比
内存释放时机 作用域末尾确定析构(RAII) GC 语言不确定(异步回收);C 手动
堆数据赋值 默认 Move(单一所有者) C++ 默认拷贝;JS/Go 引用共享
所有权转移 Move 语义(编译期验证) C++ std::move(手动);Java/JS 无此概念
共享数据 借用(&/&mut)或计数共享(Rc/Arc JS/Python 无限制共享(GC 保护);Go 无限制指针共享
循环引用 编译器拒绝(除非用 Rc + Weak GC 语言可以(标记清除处理)
数据竞争防护 借用规则 + Send/Sync trait 所有语言依赖运行时锁(无编译期检查)

学完本文后,下一步学什么?

  • 生命周期'a 注解的语义、多个生命周期参数、函数返回引用的约束推导
  • 泛型与 Trait:泛型类型的所有权行为、Deref/Drop/Clone/Copy trait 的完整体系
  • Unsafe Rust:裸指针、unsafe implunsafe fn,何时需要绕过所有权规则
  • Pin 与自引用结构体Pin<T> 如何解决"结构体字段引用自身"的内存布局问题
  • Async Rust:async/await 中所有权和借用的特殊行为(Future 是状态机,所有权在其中流转)

作者:左扬  |  发表于 2026 年  |  水平有限,欢迎指正