深入Rust核心:彻底搞懂指针、引用与智能指针指针,是通往底层世界的大门,也是许多程序员既爱又恨的概念。在C/C++中,它赋予了我们直接操控内存的无上权力,但也带来了悬垂指针、内存泄漏等无尽的烦恼。那么,以“安全”著称的Rust是如何处理指针的呢?它又是如何做到既能媲美C的性能,又
指针,是通往底层世界的大门,也是许多程序员既爱又恨的概念。在 C/C++ 中,它赋予了我们直接操控内存的无上权力,但也带来了悬垂指针、内存泄漏等无尽的烦恼。那么,以“安全”著称的 Rust 是如何处理指针的呢?它又是如何做到既能媲美 C 的性能,又能保证内存安全的呢?
本文将带你踏上一场从内存地址到高级抽象的探索之旅。我们将从最基本的“什么是指针”讲起,厘清 Rust 中引用 (&)、原始指针 (*const T) 和智能指针之间的区别与联系。你不仅会看到清晰的代码示例,还将理解 unsafe Rust 为何存在,以及何时应该使用它。最后,我们将为你全面梳理 Rust 强大的智能指针生态,助你写出既安全又高效的 Rust 代码。
static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];
fn main() {
let a = 42;
let b = &B;
let c = &C;
println!("a: {}, b: {:p}, c: {:p}", a, b, c);
}
point_demo on master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base
➜ cargo run
Compiling point_demo v0.1.0 (/Users/qiaopengjun/rust/point_demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.44s
Running `target/debug/point_demo`
a: 42, b: 0x1023dc660, c: 0x1023dc66a
point_demo on master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base
➜
使用更复杂的类型展示指针内部的区别
use std::mem::size_of;
static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];
fn main() {
// let a = 42;
// let b = &B;
// let c = &C;
// println!("a: {}, b: {:p}, c: {:p}", a, b, c);
let a: usize = 42;
let b: Box<[u8]> = Box::new(B);
let c: &[u8; 11] = &C;
println!("a (unsigned 整数):");
println!(" 地址: {:p}", &a);
println!(" 大小: {:?} bytes", size_of::<usize>());
println!(" 值: {:?}\n", a);
println!("b (B 装在 Box 里):");
println!(" 地址: {:p}", &b);
println!(" 大小: {:?} bytes", size_of::<Box<[u8]>>());
println!(" 指向: {:p}\n", b);
println!("c (C 的引用):");
println!(" 地址: {:p}", &c);
println!(" 大小: {:?} bytes", size_of::<&[u8; 11]>());
println!(" 指向: {:p}\n", c);
println!("B (10 bytes 的数组):");
println!(" 地址: {:p}", &B);
println!(" 大小: {:?} bytes", size_of::<[u8; 10]>());
println!(" 值: {:?}\n", B);
println!("C (11 bytes 的数字):");
println!(" 地址: {:p}", &C);
println!(" 大小: {:?} bytes", size_of::<[u8; 11]>());
println!(" 值: {:?}\n", C);
}
point_demo on master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base
➜ cargo run
Compiling point_demo v0.1.0 (/Users/qiaopengjun/rust/point_demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.19s
Running `target/debug/point_demo`
a (unsigned 整数):
地址: 0x16dda9a08
大小: 8 bytes
值: 42
b (B 装在 Box 里):
地址: 0x16dda9a10
大小: 16 bytes
指向: 0x12b606ba0
c (C 的引用):
地址: 0x16dda9a30
大小: 8 bytes
指向: 0x10208d7ba
B (10 bytes 的数组):
地址: 0x10208d7b0
大小: 10 bytes
值: [99, 97, 114, 114, 121, 116, 111, 119, 101, 108]
C (11 bytes 的数字):
地址: 0x10208d7ba
大小: 11 bytes
值: [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0]
point_demo on master [?] is 📦 0.1.0 via 🦀 1.67.1 via 🅒 base
➜
它创建了一个与前图更加相似的内存地址布局
use std::borrow::Cow;
use std::ffi::CStr;
use std::os::raw::c_char;
static B: [u8; 10] = [99, 97, 114, 114, 121, 116, 111, 119, 101, 108];
static C: [u8; 11] = [116, 104, 97, 110, 107, 115, 102, 105, 115, 104, 0];
fn main() {
let a = 42;
let b: String;
let c: Cow<str>;
unsafe {
let b_ptr = &B as * const u8 as *mut u8;
b = String::from_raw_parts(b_ptr, 10, 10);
let c_ptr = &C as *const u8 as *const c_char;
c = CStr::from_ptr(c_ptr).to_string_lossy();
}
println!("a: {}, b: {}, c: {}", a, b, c);
}
fn main() {
let a: i64 = 42;
let a_ptr = &a as *const i64;
println!("a: {} ({:p})", a, a_ptr);
}
fn main() {
let a: i64 = 42;
let a_ptr = &a as *const i64;
let a_addr: usize = unsafe {std::mem::transmute(a_ptr)};
println!("a: {} ({:p}...0x{:x})", a, a_ptr, a_addr + 7);
}
名称 | 简介 | 强项 | 弱项 |
---|---|---|---|
Raw Pointer | mut T 和const T,自由基,闪电般块,极其 Unsafe | 速度、与外界交互 | Unsafe |
Box<T> |
可把任何东西都放在Box里。可接受几乎任何类型的长期存储。新的安全编程时代的主力军。 | 将值集中存储在 Heap | 大小增加 |
Rc<T> |
是Rust的能干而吝啬的簿记员。它知道谁借了什么,何时借了什么 | 对值的共享访问 | 大小增加;运行时成本;线程不安全 |
Arc<T> |
是Rust的大使。它可以跨线程共享值,保证这些值不会相互干扰 | 对值的共享访问;线程安全 | 大小增加;运行时成本 |
Cell<T> |
变态专家,具有改变不可变值的能力 | 内部可变性 | 大小增加;性能 |
RefCell<T> |
对不可变引用执行改变,但有代价 | 内部可变性;可与仅接受不可变引用的Rc、Arc嵌套使用 | 大小增加;运行时成本;缺乏编译时保障 |
Cow<T> |
封闭并提供对借用数据的不可变访问,并在需要修改或所有权时延迟克隆数据 | 当只是只读访问时避免写入 | 大小可能会增大 |
String | 可处理可变长度的文本,展示了如何构建安全的抽象 | 动态按需增长;在运行时保证正确编码 | 过度分配内存大小 |
Vec<T> |
程序最常用的存储系统;它在创建和销毁值时保持数据有序 | 动态按需增长 | 过度分配内存大小 |
RawVec<T> |
是Vec<T> 和其它动态大小类型的基石;知道如何按需给你的数据提供一个家 |
动态按需增长;与内存分配器一起配合寻找空间 | 不直接适用于您的代码 |
Unique<T> |
作为值的唯一所有者,可保证拥有完全控制权 | 需要独占值的类型(如 String)的基础 | 不适合直接用于应用程序代码 |
Shared<T> |
分享所有权很难,但他使生活更轻松 | 共享所有权;可以将内存与T的宽度对齐,即使是空的时候 | 不适合直接用于应用程序代码 |
经过本文的系统性梳理,我们深入了解了 Rust 从底层到高层的指针体系。我们从指针的基本概念——内存地址的抽象开始,理解了 Rust 如何通过“引用”这一概念,在编译期就为我们提供了强大的安全保障。
核心要点回顾:
总而言之,Rust 并非消除了指针,而是驯服了它。通过掌握引用、原始指针和智能指针这“三驾马车”,你就能在享受 Rust 带来的内存安全的同时,写出不输于 C/C++ 的高性能底层代码。希望这篇文章能成为你精通 Rust 道路上的一块坚实基石。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!