内存管理
内存管理
和 C#/.NET 一样,Rust 也强调 memory-safety,用于避免大量与内存访问相关的 漏洞与缺陷。区别在于:Rust 主要在编译期保证内存安全,而不是依赖类似 CLR 的 运行时检查。一个常见例外是数组越界检查,它依然会在运行时发生(无论是 Rust 编译器生成的 机器码还是 .NET JIT 代码)。
和 C# 一样,Rust 也能写 unsafe 代码,并且关键字都叫 unsafe,
用于显式标记“编译器不再保证内存安全”的代码区域。
Rust 没有 GC。内存管理由所有权系统驱动:在 safe Rust 中,值一旦不再被使用, 会在离开作用域时及时释放。编译器通过静态分析执行 ownership 规则,规则不满足时, 直接编译失败。
在 .NET 中,开发者通常无需显式思考“谁拥有这块内存”;GC 会从根对象出发追踪引用并回收。 而 Rust 把所有权、借用、生命周期前置为编码期必答题。这会影响函数签名、类型设计、数据结构 以及并发访问方式。Rust 还能在编译期识别很多潜在的 竞态问题 与数据破坏风险。
Rust 中,一块数据在某一时刻只能有一个所有者。编译器会跟踪生命周期 (见 lifetimes)与所有权流转。所有权转移称为 move:
#![allow(dead_code, unused_variables)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 12, y: 34 }; // a 拥有 Point
let b = a; // 所有权移动到 b
println!("{}, {}", a.x, a.y); // 编译错误:a 已失效
}修正方式如下:
fn main() {
let a = Point { x: 12, y: 34 };
let b = a;
println!("{}, {}", b.x, b.y); // 使用 b
} // b 离开作用域时被 dropmain 结束时,a 和 b 都离开作用域,但只有当前持有所有权的 b 会触发释放。
Rust 的 struct 可通过实现 Drop trait 自定义析构行为:
在 C# 中可粗略类比 finalizer 与 IDisposable:
-
finalizer 由 GC 在未来某个时刻调用,时间不确定
-
Rust
drop由作用域与生命周期决定,时机确定 -
.NET 里更接近 Rust
Drop语义的是IDisposable+using所形成的确定释放
Rust 还有 'static 生命周期,可粗略类比 C# 类型上的静态只读数据的全局存活期。
虽然“单一所有者”看起来很严格,但 Rust 支持共享所有权:Rc<T> 通过引用计数管理共享。
每次 Rc::clone 增加计数,克隆被 drop 时减少计数,降到 0 后释放底层值。
#![allow(dead_code, unused_variables)]
use std::rc::Rc;
struct Point {
x: i32,
y: i32,
}
impl Drop for Point {
fn drop(&mut self) {
println!("Point dropped!");
}
}
fn main() {
let a = Rc::new(Point { x: 12, y: 34 });
let b = Rc::clone(&a); // 与 b 共享
println!("a = {}, {}", a.x, a.y);
println!("b = {}, {}", b.x, b.y);
}
// 输出:
// a = 12, 34
// b = 12, 34
// Point dropped!要点:
Point实现了Drop,被销毁时会打印消息- 被共享的是
Rc智能指针,a/b各自持有一个指针克隆 - 两个克隆都释放后,引用计数归零,底层
Point才会被 drop
Rc 不是线程安全的。多线程共享所有权时应使用 Arc。
在 .NET 中,值类型通常栈分配、引用类型通常堆分配。Rust 不按“类型类别”决定分配位置:
enum / struct 默认都可在栈上;需要堆分配时,可用 Box 装箱。
let stack_point = Point { x: 12, y: 34 };
let heap_point = Box::new(Point { x: 12, y: 34 });Box、Rc、Arc 都是智能指针,但语义不同:
Box独占所有权Rc共享所有权(单线程)Arc共享所有权(线程安全)
最后,C# 里 new 是语言关键字;Rust 里的 new 通常只是约定俗成的关联函数名,
并非语言保留构造语义。