mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1305 字
4 分钟
为什么 Rust 有所有权系统
2024-04-27

Rust 是近年来最受关注的编程语言之一,其核心创新——所有权系统(Ownership System)——让它在没有垃圾回收(GC)的情况下实现了内存安全。这个设计让很多开发者既好奇又困惑:为什么 Rust 要设计这样一套看似复杂的规则?

一、内存安全:程序员的永恒难题#

1.1 内存安全的三大问题#

在 Rust 出现之前,内存管理主要依赖两种方式:手动管理(C/C++)和垃圾回收(Java/Python)。这两种方式各有问题:

mindmap root((内存安全问题)) 悬垂指针 访问已释放内存 使用后释放 Use-After-Free 内存泄漏 忘记释放内存 循环引用 数据竞争 多线程并发访问 读写冲突 缓冲区溢出 数组越界访问 栈溢出攻击

这些问题导致的后果:

问题类型后果典型案例
悬垂指针程序崩溃/安全漏洞Heartbleed 漏洞
内存泄漏内存耗尽/性能下降长时间运行的服务器
数据竞争数据损坏/不可预测多线程计数器错误
缓冲区溢出安全漏洞/程序崩溃Morris 蠕虫、Code Red 蠕虫

1.2 传统解决方案的局限#

flowchart TB subgraph 手动管理 M1[C/C++] --> M2[完全控制] M2 --> M3[高性能] M2 --> M4[易出错] M4 --> M5[安全隐患] end subgraph 垃圾回收 G1[Java/Python] --> G2[自动管理] G2 --> G3[安全] G2 --> G4[运行时开销] G4 --> G5[GC 停顿] end subgraph 所有权系统 R1[Rust] --> R2[编译时检查] R2 --> R3[零运行时开销] R2 --> R4[内存安全] end style M5 fill:#f66 style G5 fill:#f96 style R3 fill:#6f6 style R4 fill:#6f6

手动管理的问题

// C 语言中的典型错误
void use_after_free() {
int* ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
// 错误:访问已释放的内存
printf("%d\n", *ptr); // 悬垂指针!
}
void memory_leak() {
int* ptr = malloc(sizeof(int) * 1000);
// 忘记 free(ptr)
// 内存泄漏!
}
void double_free() {
int* ptr = malloc(sizeof(int));
free(ptr);
free(ptr); // 双重释放!
}

垃圾回收的问题

timeline title GC 带来的运行时问题 程序运行 : 分配对象 : 对象可达 GC 触发 : 标记阶段 : 扫描所有可达对象 : 清除阶段 : 回收不可达对象 Stop-The-World : 所有线程暂停 : 用户感知卡顿
// Java 中的 GC 问题示例
public class GCIssue {
public static void main(String[] args) {
// 大量临时对象创建
for (int i = 0; i < 1_000_000; i++) {
String temp = new String("temporary " + i);
// temp 很快变得不可达
// 但要等 GC 运行才会回收
}
// GC 触发时可能导致明显停顿
}
}

1.3 Rust 的选择:第三条道路#

Rust 的设计目标:

flowchart LR A[设计目标] --> B[内存安全<br/>无 GC 开销] A --> C[高性能<br/>零成本抽象] A --> D[并发安全<br/>数据竞争自由] B --> E[所有权系统] C --> E D --> E E --> F[编译时保证] style E fill:#f96 style F fill:#6f6

Rust 的核心洞见

如果能在编译时通过静态分析确定内存的生命周期,就不需要运行时的垃圾回收。

二、所有权系统的三大规则#

2.1 规则一:每个值有且只有一个所有者#

flowchart TB subgraph 所有权规则 R1["规则一:每个值有唯一的所有者"] R2["规则二:同一时刻只能有一个所有者"] R3["规则三:所有者离开作用域时值被释放"] end R1 --> E1[明确的生命周期] R2 --> E2[避免重复释放] R3 --> E3[自动内存释放] E1 --> S[内存安全] E2 --> S E3 --> S style S fill:#6f6
fn main() {
// s 是字符串 "hello" 的所有者
let s = String::from("hello");
// s 离开作用域,内存自动释放
}

2.2 规则二:值可以被移动或借用#

flowchart LR A[值的使用方式] --> B[移动 Move] A --> C[借用 Borrow] B --> B1[所有权转移] B1 --> B2[原变量失效] C --> C1[临时使用] C1 --> C2[所有权不变] C --> D{借用类型} D --> E[不可变借用 &T] D --> F[可变借用 &mut T] style B2 fill:#f96 style C2 fill:#6f6

移动语义

fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 移动到 s2
// println!("{}", s1); // 错误!s1 已失效
println!("{}", s2); // 正确
}
flowchart TB subgraph 移动前 S1_1["s1"] --> P1["指针"] --> H1["堆: 'hello'"] end subgraph 移动后 S1_2["s1 (失效)"] -.->|无权访问| X[""] S2["s2"] --> P2["指针"] --> H2["堆: 'hello'"] end style X fill:#f66 style S2 fill:#6f6

借用语义

fn main() {
let s1 = String::from("hello");
// 借用:s1 仍然拥有所有权
let len = calculate_length(&s1);
println!("'{}' 的长度是 {}", s1, len); // s1 仍然可用
}
fn calculate_length(s: &String) -> usize {
s.len()
// s 是借用,离开作用域不会释放内存
}

2.3 规则三:借用规则#

flowchart TB A[借用规则] --> B[多个不可变借用] A --> C[一个可变借用] A --> D[不可变与可变互斥] B --> B1["&T 可以同时存在多个"] C --> C1["&mut T 同一时刻只能有一个"] D --> D1["有 &mut T 时不能有 &T"] D --> D2["有 &T 时不能有 &mut T"] style B1 fill:#6f6 style C1 fill:#6f6 style D1 fill:#f96 style D2 fill:#f96
操作代码示例合法性
多个不可变借用let r1 = &s; let r2 = &s;合法
一个可变借用let r = &mut s;合法
可变 + 不可变let r1 = &s; let r2 = &mut s;非法
多个可变借用let r1 = &mut s; let r2 = &mut s;非法
fn main() {
let mut s = String::from("hello");
// 多个不可变借用
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// 可变借用(之前的不可变借用已结束)
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3);
// 错误示例:同时存在可变和不可变借用
// let r4 = &s;
// let r5 = &mut s;
// println!("{} {}", r4, r5); // 编译错误!
}

三、借用检查器:编译时的守护者#

3.1 借用检查器的工作原理#

flowchart TB A[源代码] --> B[词法分析] B --> C[语法分析] C --> D[类型检查] D --> E[借用检查] E --> F{检查通过?} F -->|是| G[生成机器码] F -->|否| H[编译错误] subgraph 借用检查内容 E1[生命周期是否有效] E2[借用规则是否遵守] E3[数据竞争是否可能] end E --> E1 E --> E2 E --> E3 style G fill:#6f6 style H fill:#f66

借用检查器追踪什么

fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // ---------+
}

编译错误:

error[E0597]: `x` does not live long enough
--> src/main.rs:6:13
|
6 | r = &x;
| ^^ borrowed value does not live long enough
7 | }
| - `x` dropped here while still borrowed
8 |
9 | println!("r: {}", r);
| - borrow later used here

3.2 借用检查器如何防止错误#

sequenceDiagram participant Code as 源代码 participant BC as 借用检查器 participant Result as 检查结果 Code->>BC: 提交代码 BC->>BC: 分析变量生命周期 BC->>BC: 追踪借用关系 alt 借用合法 BC->>Result: 通过检查 Result->>Code: 编译成功 else 借用非法 BC->>Result: 发现问题 Result->>Code: 编译错误 + 详细提示 end

防止 Use-After-Free

fn main() {
let s = String::from("hello");
let ptr = &s;
drop(s); // 显式释放
// println!("{}", ptr); // 编译错误!
// 借用检查器阻止了悬垂指针
}

防止数据竞争

use std::thread;
fn main() {
let mut data = vec![1, 2, 3];
let handle = thread::spawn(|| {
// data.push(4); // 编译错误!
// 无法捕获可变引用
});
data.push(5); // 主线程修改
handle.join().unwrap();
}

3.3 非词法作用域生命周期(NLL)#

Rust 2018 引入了 NLL(Non-Lexical Lifetimes),使借用检查更智能:

flowchart TB subgraph 词法作用域 L1["let r = &s;"] --> L2["// ..."] L2 --> L3["// 使用 s"] L3 --> L4["// r 作用域结束"] end subgraph NLL N1["let r = &s;"] --> N2["// 最后使用 r"] N2 --> N3["// 借用结束"] N3 --> N4["// s 可再次借用"] end style L4 fill:#f96 style N3 fill:#6f6
fn main() {
let mut s = String::from("hello");
let r = &s;
println!("{}", r); // r 的最后一次使用
// NLL:借用已结束,可以再次可变借用
s.push_str(" world"); // 在 Rust 2018+ 中合法
println!("{}", s);
}

四、生命周期:明确的引用有效期#

4.1 什么是生命周期?#

flowchart TB A[生命周期] --> B[引用的有效范围] B --> C[确保引用始终有效] A --> D[生命周期标注] D --> E[帮助编译器推断] E --> F[泛型生命周期参数] style C fill:#6f6

生命周期是 Rust 独特的概念,用于追踪引用的有效范围:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
flowchart LR subgraph 函数 longest X["x: &'a str"] --> R["返回 &'a str"] Y["y: &'a str"] --> R end subgraph 调用 S1["string1: 'a"] --> LONG["longest()"] S2["string2: 'a"] --> LONG LONG --> RES["结果: 'a"] end style R fill:#f96 style RES fill:#6f6

4.2 生命周期标注规则#

mindmap root((生命周期规则)) 每个引用都有生命周期 编译器通常能推断 复杂情况需标注 生命周期标注语法 'a 表示泛型生命周期 可以有多个参数 省略规则 每个输入引用独立 只有一个输入时输出同生命周期 方法有 &self 时适用

三条省略规则

// 规则示例
// 1. 每个输入引用独立
fn foo(x: &i32, y: &i32); // 实际是 fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
// 2. 只有一个输入,输出同生命周期
fn foo(x: &str) -> &str; // 实际是 fn foo<'a>(x: &'a str) -> &'a str
// 3. 有 &self 时,输出借用 self
struct Foo;
impl Foo {
fn bar(&self, x: &str) -> &str; // 输出借用 self
}

4.3 生命周期的实际应用#

sequenceDiagram participant Main as main() participant Longest as longest() participant Result as 返回值 Main->>Main: 创建 string1 Main->>Main: 创建 string2 Main->>Longest: 调用 longest(&string1, &string2) Note over Longest: 比较长度 Longest->>Result: 返回较长的引用 Main->>Main: 使用 result Note over Main: string1, string2 仍然有效 Main->>Result: 打印 result
fn main() {
let string1 = String::from("long string");
{
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
println!("最长的是: {}", result);
}
// println!("{}", result); // 错误!result 的生命周期已结束
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

4.4 结构体中的生命周期#

// 结构体持有引用时必须标注生命周期
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().unwrap();
// first_sentence 借用 novel
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("{}", excerpt.part);
// excerpt 必须在 novel 之前离开作用域
}

五、与其他语言的对比#

5.1 Rust vs C++ RAII#

flowchart TB subgraph C++ RAII C1[资源获取即初始化] C2[析构函数自动释放] C3[依赖程序员习惯] C4[可能忘记使用智能指针] end subgraph Rust 所有权 R1[所有权即资源管理] R2[编译器强制检查] R3[不可能忘记释放] R4[不可能双重释放] end style C4 fill:#f96 style R3 fill:#6f6 style R4 fill:#6f6

C++ RAII 示例

#include <memory>
#include <iostream>
class Resource {
public:
Resource() { std::cout << "Acquired\n"; }
~Resource() { std::cout << "Released\n"; }
};
void example() {
// 正确使用智能指针
std::unique_ptr<Resource> ptr = std::make_unique<Resource>();
// 但这不是强制的!
Resource* raw = new Resource(); // 可能忘记 delete
// delete raw; // 忘记释放?
// 可能的问题
std::unique_ptr<Resource> ptr2 = std::move(ptr);
// ptr->doSomething(); // 运行时错误,不是编译错误
}

Rust 所有权示例

struct Resource;
impl Resource {
fn new() -> Self {
println!("Acquired");
Resource
}
}
impl Drop for Resource {
fn drop(&mut self) {
println!("Released");
}
}
fn example() {
let _r = Resource::new(); // 自动管理
// let r2 = r; // 移动后 r 失效
// println!("{:?}", r); // 编译错误!
// 不可能忘记释放
// 不可能双重释放
}

5.2 Rust vs Swift ARC#

flowchart TB subgraph Swift ARC S1[自动引用计数] S2[运行时开销] S3[循环引用问题] S4[weak/unowned 解决] end subgraph Rust 所有权 R1[编译时检查] R2[零运行时开销] R3[编译器检测循环] R4[Rc/Arc 可选使用] end style S2 fill:#f96 style S3 fill:#f96 style R2 fill:#6f6

Swift ARC 示例

class Person {
var name: String
var friend: Person? // 强引用,可能导致循环
init(name: String) {
self.name = name
print("\(name) created")
}
deinit {
print("\(name) deinitialized")
}
}
func createCycle() {
let alice = Person(name: "Alice")
let bob = Person(name: "Bob")
alice.friend = bob // alice -> bob
bob.friend = alice // bob -> alice (循环引用!)
// 内存泄漏!需要使用 weak var
}

Rust 所有权示例

use std::rc::Rc;
use std::cell::RefCell;
struct Person {
name: String,
friend: RefCell<Option<Rc<Person>>>,
}
impl Drop for Person {
fn drop(&mut self) {
println!("{} released", self.name);
}
}
fn main() {
let alice = Rc::new(Person {
name: "Alice".to_string(),
friend: RefCell::new(None),
});
let bob = Rc::new(Person {
name: "Bob".to_string(),
friend: RefCell::new(None),
});
// 循环引用会阻止释放
*alice.friend.borrow_mut() = Some(bob.clone());
*bob.friend.borrow_mut() = Some(alice.clone());
// 需要使用 Weak<T> 打破循环
}

5.3 三种方案对比#

xychart-beta title "内存管理方案对比" x-axis ["安全性", "性能", "开发体验", "学习曲线"] y-axis "评分" 0 --> 10 bar [4, 9, 6, 8] bar [8, 6, 9, 9] bar [10, 10, 5, 3]
特性C++ 手动/RAIISwift ARCRust 所有权
内存安全依赖程序员运行时保证编译时保证
运行时开销引用计数开销
循环引用需手动处理weak 引用Weak 或编译器
并发安全不保证不保证编译时保证
学习曲线中等简单陡峭
错误发现时机运行时运行时编译时

六、所有权系统的代价与收益#

6.1 学习曲线:最大的代价#

flowchart LR A[开始学习 Rust] --> B[所有权概念] B --> C[借用规则] C --> D[生命周期标注] D --> E[智能指针] B --> B1["困难度: "] C --> C1["困难度: "] D --> D1["困难度: "] E --> E1["困难度: "] style B fill:#f96 style D fill:#f96

常见的学习障碍

// 障碍一:理解移动语义
fn main() {
let v = vec![1, 2, 3];
let v2 = v;
// println!("{:?}", v); // 错误!v 已移动
}
// 障碍二:生命周期标注
fn first_word<'a>(s: &'a str) -> &'a str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// 障碍三:自引用结构
struct SelfRef {
value: String,
pointer: *const String, // 原始指针绕过借用检查
// 或者使用 Pin<Box<T>>
}

6.2 编译器错误信息#

Rust 编译器提供详细的错误信息:

fn main() {
let s = String::from("hello");
let s1 = &s;
let s2 = &mut s;
println!("{} {}", s1, s2);
}

编译错误:

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:4:14
|
3 | let s1 = &s;
| -- immutable borrow occurs here
4 | let s2 = &mut s;
| ^^^^^^ mutable borrow occurs here
5 | println!("{} {}", s1, s2);
| -- immutable borrow later used here

6.3 收益:编译时保证的内存安全#

flowchart TB A[所有权系统] --> B[编译时检查] B --> C[内存安全] B --> D[并发安全] B --> E[性能保证] C --> C1[无悬垂指针] C --> C2[无数据竞争] C --> C3[无缓冲区溢出] D --> D1[多线程安全] D --> D2[Send/Sync trait] E --> E1[零成本抽象] E --> E2[无 GC 停顿] E --> E3[可预测性能] style C fill:#6f6 style D fill:#6f6 style E fill:#6f6

实际案例

项目使用 Rust 前后对比
Firefox CSS 引擎减少 50% 内存相关 bug
Dropbox 存储引擎替代 C++,消除内存泄漏
Cloudflare pingora比 Nginx 更快,无内存安全问题
Discord 服务性能提升,减少 GC 停顿
AWS Firecracker安全的微虚拟机,内存隔离

6.4 性能对比#

// Rust: 零成本抽象
fn sum_rust(data: &[i32]) -> i32 {
data.iter().sum() // 编译后与手写循环一样高效
}
// Java: 有 GC 开销
public int sumJava(int[] data) {
int total = 0;
for (int x : data) {
total += x;
}
return total;
// GC 可能在此期间运行
}
xychart-beta title "内存管理开销对比" x-axis ["分配", "访问", "释放", "GC 停顿"] y-axis "相对开销" 0 --> 100 bar [10, 5, 8, 0] bar [15, 10, 5, 40]

七、实战:所有权系统的应用#

7.1 构建安全的字符串处理#

fn process_string(input: &str) -> String {
let mut result = String::new();
for word in input.split_whitespace() {
if word.len() > 3 {
result.push_str(word);
result.push(' ');
}
}
result.trim_end().to_string()
}
fn main() {
let original = String::from("hello world from rust");
// 借用 original,不获取所有权
let processed = process_string(&original);
println!("原始: {}", original); // original 仍然可用
println!("处理后: {}", processed);
}

7.2 安全的并发编程#

use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc: 原子引用计数,允许多线程共享
// Mutex: 互斥锁,保证安全访问
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!("结果: {}", *counter.lock().unwrap());
}
flowchart TB subgraph 线程安全机制 A[Arc<T>] --> A1[原子引用计数] A1 --> A2[多线程共享所有权] M[Mutex<T>] --> M1[互斥访问] M1 --> M2[同一时刻一个线程访问] end A --> S[安全的多线程编程] M --> S style S fill:#6f6

7.3 构建安全的数据结构#

// 简单的链表节点
pub struct List<T> {
head: Link<T>,
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None }
}
pub fn push(&mut self, elem: T) {
let new_node = Box::new(Node {
elem,
next: self.head.take(),
});
self.head = Some(new_node);
}
pub fn pop(&mut self) -> Option<T> {
self.head.take().map(|node| {
self.head = node.next;
node.elem
})
}
}

八、智能指针:所有权的扩展#

8.1 智能指针类型#

mindmap root((智能指针)) Box<T> 堆分配 单一所有权 已知大小 Rc<T> 引用计数 多重所有权 单线程 Arc<T> 原子引用计数 多重所有权 多线程安全 RefCell<T> 内部可变性 运行时借用检查 Mutex<T> 互斥锁 内部可变性 多线程安全

8.2 Box:堆分配#

fn main() {
// 在堆上分配
let b = Box::new(5);
println!("b = {}", b);
// 递归类型
#[derive(Debug)]
enum List<T> {
Cons(T, Box<List<T>>),
Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{:?}", list);
}

8.3 Rc:引用计数#

use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // 引用计数 +1
let c = Rc::clone(&a); // 引用计数 +1
println!("引用计数: {}", Rc::strong_count(&a)); // 3
}

8.4 RefCell:内部可变性#

use std::cell::RefCell;
fn main() {
let x = RefCell::new(5);
{
let mut mutable = x.borrow_mut();
*mutable += 1;
}
println!("{}", x.borrow()); // 6
}

九、所有权系统的设计哲学#

9.1 权限与责任#

flowchart TB A[所有权哲学] --> B[权限] A --> C[责任] B --> B1[读取权限 &T] B --> B2[写入权限 &mut T] B --> B3[所有权转移] C --> C1[确保引用有效] C --> C2[避免数据竞争] C --> C3[正确管理资源] B1 --> D[编译器强制] B2 --> D C1 --> D C2 --> D style D fill:#6f6

9.2 零成本抽象原则#

“What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.”

— Bjarne Stroustrup

Rust 的所有权系统完美体现了这一原则:

// 高级抽象
let sum: i32 = (1..=100).sum();
// 编译后与手写循环一样高效
// 没有额外的运行时开销

9.3 类型系统的力量#

flowchart TB A[类型系统] --> B[编译时检查] B --> C[所有权规则] B --> D[生命周期检查] B --> E[类型安全] C --> F[内存安全] D --> F E --> F F --> G[运行时零开销] style F fill:#6f6 style G fill:#6f6

十、总结与展望#

10.1 所有权系统的核心价值#

mindmap root((所有权系统)) 内存安全 无悬垂指针 无双重释放 无数据竞争 性能保证 零运行时开销 无 GC 停顿 可预测性能 并发安全 编译时检查 Send/Sync trait 无畏并发 工程价值 早期错误发现 明确的所有权语义 文档化资源管理

10.2 适用场景#

场景是否适合 Rust原因
系统编程非常适合内存安全 + 高性能
嵌入式开发非常适合零运行时,内存可预测
Web 服务适合高并发,无 GC 停顿
命令行工具适合性能好,开发效率高
WebAssembly非常适合小体积,无运行时
快速原型开发可能较慢学习曲线陡峭
简单脚本可能过重编译时间,复杂度

10.3 学习建议#

flowchart TD A[学习 Rust] --> B[基础语法] B --> C[所有权概念] C --> D[借用与生命周期] D --> E[实践项目] C --> C1["重点理解: - 每个值只有一个所有者 - 借用规则 - 移动 vs 复制"] D --> D1["难点突破: - 生命周期标注 - 智能指针选择 - 错误信息解读"] E --> E1["推荐项目: - 命令行工具 - Web 服务 - 系统工具"] style C fill:#f96 style D fill:#f96 style E fill:#6f6

10.4 未来展望#

timeline title Rust 所有权系统演进 2010 : Rust 项目启动 : 所有权概念诞生 2012 : 借用检查器成熟 2015 : Rust 1.0 发布 : 稳定的所有权系统 2018 : NLL 引入 : 借用检查更智能 2021 : Rust 基金会成立 : 广泛采用 2024 : 异步 Rust 成熟 : 所有权 + async 未来 : 更好的错误信息 : 更低的学习曲线 : 更广泛的采用

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么 Rust 有所有权系统
https://blog.souloss.com/posts/why-the-design/why-rust-has-ownership-system/
作者
Souloss
发布于
2024-04-27
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时