- 1. 前言
- 2. 路径运算符
- 3. 问号
- 4. 胖指针
- 5. FFI 类型转换
- 6. Struct 内存布局与 repr
- 7. 所有权模型:Rust vs C++
- 8. 拷贝语义 vs 移动语义
- 9. 智能指针
- 10. RAII 与析构 (Drop)
- 11. 回调与虚表 (Vtable)
- 12. 线程安全:Send/Sync 推导
- 13. Pin 与不可移动类型
- 14. 生命周期与借用
- 15. 常见陷阱与最佳实践
本文做了一些常见知识点归纳,方便对知识的梳理和应用,比如:Rust路径运算符,其中就涉及turbofish运算符,是Rust中一种特殊做法。
1. 前言
Rust在设计上有自己很多独特的思路和设计,在学习Rust过程中,对常见知识点进行归纳总结,可以加快加深对Rust的掌握和应用。
2. 路径运算符
| 用法 | 示例 | 说明 |
|---|---|---|
| 模块路径 | crate::utils::helper() |
访问模块中的函数 |
| 类型的关联函数(静态方法) | String::from("abc") |
调用类型的静态/关联函数 |
| 枚举构造器 | Option::Some(42) |
枚举的某个变体 |
| 泛型类型显式指定(turbofish) | "123".parse::<i32>() |
指定 .parse() 的返回类型 |
| 常量访问 | std::f32::consts::PI |
访问某个类型下定义的常量 |
类型路径(如 as 类型转换) |
value as std::ffi::c_void |
明确写出类型的完整路径 |
use 关键字,可以根据路径这个思路,导入:模块、类型、枚举、函数、宏、常量、嵌套(大括号),并支持重命名,甚至可以导入结构体字段(如果字段是pub的)。如果是全局导入,::写在最前面。
但是use不能引入泛型类型参数,及 ::<T>,也不能引入方法调用链,因为use知识声明作用域,不具备执行能力。
为什么turbofish也能成为路径呢?
可以理解为,函数parse()是一个泛型函数,从顺序上讲,先有函数,才有函数依赖的泛型,所以,泛型属于函数的下一级逻辑干掉安排泛型属于函数的路径上的下一级。于是 parse() 写成 parse::<i32>()。
那为什么不写成 parse()::<i32> 呢?parse()<::i32> 表示函数已经执行完成了,再做 ::<i32>操作。显然不行,泛型需要在函数执行前就指定好。
3. 问号
? 运算符是 Rust 的错误处理语法糖,主要用于 Result 和 Option 类型。
3.1 基本用法
// Result 的 ? 运算符
fn read_file() -> Result<String, std::io::Error> {
let content = std::fs::read_to_string("file.txt")?;
Ok(content)
}
// Option 的 ? 运算符
fn find_user(id: u32) -> Option<User> {
let user = database::find(id)?; // None 则提前返回
Some(user)
}
3.2 ? 的本质
? 实际上是以下代码的语法糖:
// ?
match result {
Ok(val) => val,
Err(e) => return Err(From::from(e)),
}
3.3 与 try! 宏的区别
// Rust 1.13 之前的写法
let f = try!(std::fs::File::open("foo"));
// 现代写法(等价)
let f = std::fs::File::open("foo")?;
4. 胖指针
胖指针(Fat Pointer)是指包含额外元数据的指针,在 Rust 中用于表示不确定大小的类型。
4.1 切片指针
// &[T] 是胖指针,包含:
// - 指向数据的指针
// - 切片长度
let slice: &[i32] = &[1, 2, 3, 4, 5];
// 内存布局: { ptr: 0x..., len: 5 }
4.2 Trait 对象指针
// &dyn Trait 是胖指针,包含:
// - 指向数据的指针
// - 指向虚表(vtable)的指针
trait Drawable {
fn draw(&self);
}
struct Circle;
impl Drawable for Circle {
fn draw(&self) { println!("Circle"); }
}
let drawable: &dyn Drawable = &Circle;
// 内存布局: { data_ptr: 0x..., vtable_ptr: 0x... }
4.3 字符串切片
// &str 也是一种"胖指针"
let s: &str = "hello";
// 包含: { ptr: 指向字符串数据, len: 字符串长度 }
4.4 自定义胖指针
// 自定义胖指针结构
#[repr(C)]
struct MyFatPtr<T> {
data: *const T,
metadata: usize,
}
5. FFI 类型转换
FFI(Foreign Function Interface)是 Rust 与 C/C++ 等其他语言互操作的基础。
5.1 原始指针类型转换
use std::ffi::c_void;
// Rust 引用转原始指针
let value = 42;
let ptr = &value as *const i32;
let mut_ptr = &mut value as *mut i32;
// 原始指针转引用(需 unsafe)
unsafe {
let ref_val = &*ptr;
}
5.2 字符串类型转换
| Rust | C++ | 特点 |
|---|---|---|
&str |
const char* |
字符串切片,不可变 |
String |
std::string |
可变字符串 |
CString |
std::unique_ptr<char[]> |
C 风格字符串,所有权转移 |
CStr |
const char* |
借用 C 字符串 |
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
// Rust String -> C 字符串
let rust_str = "hello";
let c_string = CString::new(rust_str).unwrap();
let ptr: *const c_char = c_string.as_ptr();
// C 字符串 -> Rust String
unsafe {
let c_str = CStr::from_ptr(c_ptr);
let rust_string = c_str.to_string_lossy().into_owned();
}
5.3 数值类型转换
use std::os::raw::c_int;
// Rust i32 -> C int
let rust_int: i32 = 42;
let c_int_val = rust_int as c_int;
// 安全的整数转换
fn safe_add(a: i32, b: i32) -> Result<i32, &'static str> {
a.checked_add(b).ok_or("overflow")
}
5.4 bindgen 与 C++ 互操作
bindgen 和 cxx 是 Rust 与 C++ 互操作的两个主要工具。
5.4.1 bindgen(自动生成 C 绑定)
bindgen 可以自动从 C 头文件生成 Rust FFI 绑定。
# Cargo.toml
[dependencies]
bindgen = "0.69"
libc = "0.2"
[build-dependencies]
bindgen = "0.69"
// build.rs
fn main() {
bindgen::builder()
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings")
.write_to_file("src/bindings.rs")
.expect("Couldn't write bindings!");
}
生成的 bindings.rs 包含 C 函数的 Rust 声明:
extern "C" {
pub fn c_function(arg: c_int) -> c_int;
pub fn malloc(size: usize) -> *mut c_void;
pub fn free(ptr: *mut c_void);
}
5.4.2 cxx(C++ 互操作标准库)
cxx 提供了 Rust 与 C++ 之间的安全互操作,支持:
- 智能指针映射
- 异常处理
- 跨语言结构体
- 线程安全
# Cargo.toml
cxx = "1.0"
// src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_function(input: i32) -> i32;
}
extern "C++" {
include!("header.h");
fn cpp_function(input: i32) -> i32;
}
unsafe extern "C++" {
include!("raw.h");
}
}
fn rust_function(input: i32) -> i32 {
input * 2
}
C++ 端使用:
#include "rust/cxx.h"
#include "lib.h"
int main() {
auto result = rust_function(42);
}
5.4.3 bindgen vs cxx 选择
| 特性 | bindgen | cxx |
|---|---|---|
| 语言支持 | C | C++ |
| 类型安全 | 基础 | 高层次抽象 |
| 智能指针映射 | 手动 | 自动 |
| 异常处理 | 手动 | 自动 |
| 学习曲线 | 低 | 中 |
推荐:
- 纯 C 库 → bindgen
- C++ 库 → cxx(推荐)或 bindgen + 手动封装
- 复杂 C++ 项目 → cxx + 手动补充
6. Struct 内存布局与 repr
6.1 repr 属性
Rust 提供多种 repr 属性控制结构体的内存布局:
// 默认 repr (Rust 1.0)
// 字段顺序不确定,可能添加填充对齐
struct DefaultRepr {
a: u8,
b: u64,
c: u32,
}
// C 兼容布局 (与 C struct 完全一致)
#[repr(C)]
struct CRepr {
a: u8,
b: u64, // 偏移 8 (前面有 7 字节填充)
c: u32, // 偏移 16
}
// 紧凑布局 (无填充,但可能影响性能)
#[repr(packed)]
struct PackedRepr {
a: u8,
b: u64, // 偏移 1
c: u32, // 偏移 9
}
// 透明布局 (作为单一字段的"包装")
#[repr(transparent)]
struct TransparentWrapper(Option<String>);
6.2 与 C++ 内存布局对比
// C++ 结构体 (默认布局)
struct CppStruct {
int a; // 4 字节
long b; // 8 字节 (64位)
char c; // 1 字节 + 7 填充
}; // 总大小: 24 字节
// 对应 Rust
#[repr(C)]
struct RustStruct {
a: i32,
b: i64,
c: i8,
}
关键点:跨 FFI 边界必须使用
#[repr(C)]
7. 所有权模型:Rust vs C++
7.1 核心概念对比
| 特性 | Rust | C++ |
|---|---|---|
| 所有权规则 | 单一所有者 + 借用检查 | 手动管理或智能指针 |
| 生命周期 | 编译期静态分析 | 运行时或约定 |
| 移动语义 | 默认移动(破坏原值) | 需显式 std::move |
| 借用规则 | 编译期检查 | 未强制(依赖约定) |
7.2 所有权转移模式
// Rust: 移动语义
fn take_ownership(s: String) {
println!("{}", s);
} // s 在此drop
let s = String::from("hello");
take_ownership(s);
// println!("{}", s); // 编译错误!s 已被移动
8. 拷贝语义 vs 移动语义
8.1 Copy vs Move trait
// Copy: 按位拷贝(隐式)
#[derive(Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
// Move: 所有权转移(默认行为)
struct Buffer {
data: Vec<u8>,
}
fn process(buffer: Buffer) {
// buffer 移动进函数,原变量不可用
}
8.2 C++ 对比
// C++ 拷贝语义
Point p1{1.0, 2.0};
Point p2 = p1; // 拷贝(生成副本)
// C++ 移动语义 (C++11+)
Buffer b1;
Buffer b2 = std::move(b1); // 移动,数据转移
9. 智能指针
9.1 常见的智能指针
| 指针类型 | 所有权 | 线程安全 | 适用场景 |
|---|---|---|---|
Box<T> |
独占 | 否 | 堆分配、递归类型 |
Rc<T> |
共享引用计数 | 否 | 单线程共享 |
Arc<T> |
共享引用计数 | 是 | 多线程共享 |
RefCell<T> |
独占(内部可变性) | 否 | 运行时借用检查 |
9.2 智能指针组合
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::Mutex;
// 线程安全的可修改引用
type ThreadSafe<T> = Arc<Mutex<T>>;
// Rc + RefCell(单线程)
let data = Rc::new(RefCell::new(vec![1, 2, 3]));
// Arc + Mutex(多线程)
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
10. RAII 与析构 (Drop)
10.1 Rust Drop trait
struct Resource {
id: i32,
handle: FileHandle,
}
impl Drop for Resource {
fn drop(&mut self) {
println!("Releasing resource {}", self.id);
self.handle.close();
}
}
10.2 跨边界的析构管理
// Rust 对象跨越到 C++,由 C++ 负责释放
#[no_mangle]
pub extern "C" fn create_rust_resource() -> *mut Resource {
Box::into_raw(Box::new(Resource::new()))
}
#[no_mangle]
pub extern "C" fn destroy_rust_resource(ptr: *mut Resource) {
unsafe {
Box::from_raw(ptr); // 触发 Drop
}
}
10.3 RAII 对比
// C++ RAII
class File {
public:
File(const char* path) : handle(fopen(path, "r")) {}
~File() { if (handle) fclose(handle); }
private:
FILE* handle;
};
// Rust RAII
struct File {
handle: Option<FileHandle>,
}
impl Drop for File {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
// 关闭操作
}
}
}
11. 回调与虚表 (Vtable)
11.1 函数指针回调
// Rust 回调传递给 C++
type ProgressCallback = extern "C" fn(progress: f32, user_data: *mut c_void);
#[no_mangle]
pub extern "C" fn register_progress(
callback: ProgressCallback,
user_data: *mut c_void
) {
// 保存回调供后续调用
}
11.2 虚表 (Vtable) 实现
// trait 对应 C++ 抽象类
trait Drawable {
fn draw(&self);
fn area(&self) -> f64;
}
// &dyn Trait 是胖指针,包含 vtable
struct TraitObject {
data: *mut c_void,
vtable: *const VTable,
}
12. 线程安全:Send/Sync 推导
12.1 本质
// Send: 可以在线程间传递所有权
// Sync: 可以在线程间共享引用
// 原始类型默认实现
unsafe impl Send for i32 {}
unsafe impl Sync for i32 {}
// 包含 Mutex 的类型自动实现 Send + Sync
use std::sync::Mutex;
struct Container {
data: Mutex<Vec<i32>>,
}
12.2 手动实现 Send/Sync
// 线程安全计数器
struct AtomicCounter {
value: std::sync::atomic::AtomicUsize,
}
unsafe impl Send for AtomicCounter {}
unsafe impl Sync for AtomicCounter {}
13. Pin 与不可移动类型
13.1 Pin 的本质
use std::pin::Pin;
use std::marker::PhantomPinned;
struct Unmovable {
data: [u8; 64],
_pin: PhantomPinned,
}
impl Unmovable {
fn new() -> Pin<Box<Self>> {
Box::pin(Unmovable {
data: [0; 64],
_pin: PhantomPinned,
})
}
}
13.2 自引用结构体
struct SelfReference {
data: String,
ptr: *const String, // 指向自身
}
impl SelfReference {
fn new(data: String) -> Pin<Box<Self>> {
let mut this = Box::pin(SelfReference {
data,
ptr: std::ptr::null(),
});
let ptr = &this.data as *const String;
unsafe {
this.as_mut().map_unchecked_mut(|s| {
s.ptr = ptr;
});
}
this
}
}
14. 生命周期与借用
14.1 生命周期标注
// 函数中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 结构体中的生命周期
struct Parser<'a> {
input: &'a str,
position: usize,
}
14.2 跨 FFI 的生命周期
// 关键规则:跨 FFI 边界不能使用 Rust 生命周期
// 因为 C++ 不理解 Rust 的生命周期系统
// 正确做法:返回 owned 字符串
#[no_mangle]
pub extern "C" fn get_string_owned() -> *mut c_char {
CString::new("processed").unwrap().into_raw()
}
15. 常见陷阱与最佳实践
15.1 内存安全陷阱
// 陷阱1: 悬垂指针
#[no_mangle]
pub extern "C" fn bad_function() -> *const c_char {
let s = String::from("temporary");
s.as_ptr() // ❌ 指针指向已释放的内存!
}
// 正确做法
#[no_mangle]
pub extern "C" fn good_function() -> *mut c_char {
CString::new("permanent").unwrap().into_raw()
}
15.2 最佳实践清单
// ✅ 1. 始终使用 #[repr(C)] 对齐 C 布局
// ✅ 2. 明确所有权的转移
// ✅ 3. 使用 CString/CStr 处理字符串
// ✅ 4. 使用 Pin 处理不可移动对象
// ✅ 5. 编写测试验证内存布局
#[cfg(test)]
mod tests {
#[test]
fn test_layout() {
assert_eq!(std::mem::size_of::<MyStruct>(), 24);
}
}