一、智能指针(Box、Rc、Arc、RefCell)
智能指针是兼具 “指针功能” 和 “额外语义” 的数据结构,Rust 中最常用的智能指针均实现了 Deref
(解引用)和 Drop
(自动释放)Trait,以下为核心类型详解:
1. Box<T>:简单堆分配
用途:
- 将数据从栈转移到堆,栈中仅保留堆地址;
- 解决 “递归类型大小无法确定” 问题(如链表、树);
- 作为 trait 对象实现动态多态。
范例:链表节点(解决递归类型大小问题)
// 递归类型:若不用 Box,编译器无法计算 List 的大小(无限递归)
enum List {
// Cons:保存一个中文字符串和下一个节点的 Box(堆地址)
Cons(String, Box<List>),
// Nil:链表结束标记
Nil,
}
fn main() {
// 构建中文链表:"Rust 入门" -> "智能指针" -> "Box 示例" -> 结束
let list = List::Cons(
"Rust 入门".to_string(),
Box::new(List::Cons(
"智能指针".to_string(),
Box::new(List::Cons(
"Box 示例".to_string(),
Box::new(List::Nil),
)),
)),
);
// 匹配打印链表(简化版)
match list {
List::Cons(val, next) => {
println!("链表节点:{}", val); // 输出:链表节点:Rust 入门
// 解引用 next 继续处理下一个节点(Box 实现 Deref,可自动解引用)
match *next {
List::Cons(val2, _) => println!("下一个节点:{}", val2), // 输出:下一个节点:智能指针
List::Nil => (),
}
}
List::Nil => (),
}
}
2. Rc<T>:单线程引用计数
用途:
- 单线程环境下实现 “多所有权”(多个变量共享同一堆数据);
- 通过
clone()
增加引用计数,计数为 0 时自动释放数据。
注意:Rc<T>
不可跨线程(无 Send
Trait),线程安全版本需用 Arc<T>
。
范例:共享配置(单线程多所有权)
use std::rc::Rc;
// 应用配置结构体
struct AppConfig {
app_name: String, // 应用名称
font_size: u32, // 字体大小
language: String, // 语言(如“简体中文”)
}
fn main() {
// 1. 创建堆上的配置,用 Rc 包裹
let config = Rc::new(AppConfig {
app_name: "中文编辑器".to_string(),
font_size: 16,
language: "简体中文".to_string(),
});
// 2. 多变量共享配置:clone() 仅增加引用计数(浅拷贝,不复制数据)
let config_copy1 = Rc::clone(&config);
let config_copy2 = Rc::clone(&config);
// 打印引用计数(此时为 3)
println!("配置引用计数:{}", Rc::strong_count(&config));
// 共享访问配置
println!("应用名称:{}", config.app_name);
println!("副本 1 语言:{}", config_copy1.language);
println!("副本 2 字体大小:{}", config_copy2.font_size);
}
3. Arc<T>:多线程安全引用计数
用途:
- 多线程环境下实现 “共享所有权”,内部用原子操作保证计数安全;
- 需配合
Send
+Sync
的数据(或内部可变性类型如Mutex
)。
范例:多线程共享中文任务
use std::sync::Arc;
use std::thread;
// 任务结构体(需 Send + Sync,此处满足)
struct ChineseTask {
task_id: u32,
content: String, // 任务内容(中文)
}
fn main() {
// 1. 创建 Arc 包裹的任务
let task = Arc::new(ChineseTask {
task_id: 1001,
content: "处理用户提交的中文文档".to_string(),
});
let mut handles = vec![];
// 2. 启动 3 个线程共享任务
for i in 0..3 {
// 每个线程 clone 一份 Arc(增加计数)
let task_clone = Arc::clone(&task);
let handle = thread::spawn(move || {
println!(
"线程 {} - 执行任务 {}:{}",
i, task_clone.task_id, task_clone.content
);
});
handles.push(handle);
}
// 3. 等待所有线程结束
for handle in handles {
handle.join().unwrap();
}
// 最终引用计数(主线程的 task + 3 个线程的 clone,共 4,线程结束后计数减为 1)
println!("最终引用计数:{}", Arc::strong_count(&task));
}
4. RefCell<T>:单线程内部可变性
用途:
- 单线程下突破 “不可变引用不能修改数据” 的限制,通过运行时检查保证借用规则;
- 常用搭配:
Rc<RefCell<T>>
(多所有权 + 可变访问)。
核心方法:
borrow()
:获取不可变引用(运行时检查,避免多个可变引用);borrow_mut()
:获取可变引用(同一时间仅允许一个)。
范例:Rc + RefCell 共享可变中文列表
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
// 1. Rc<RefCell<Vec<String>>>:多所有权 + 内部可变性
let chinese_words = Rc::new(RefCell::new(vec![
" Rust".to_string(),
" 智能指针".to_string(),
]));
// 2. 共享可变访问:添加中文词汇
let words_copy1 = Rc::clone(&chinese_words);
words_copy1.borrow_mut().push(" RefCell".to_string()); // 可变引用修改
let words_copy2 = Rc::clone(&chinese_words);
words_copy2.borrow_mut().push(" 示例".to_string());
// 3. 不可变访问:打印所有词汇
let words = chinese_words.borrow(); // 不可变引用
println!("中文词汇列表:{}", words.join("")); // 输出:Rust 智能指针 RefCell 示例
// 错误演示:同一时间不能同时存在可变和不可变引用(运行时 panic)
// let mut mut_ref = chinese_words.borrow_mut();
// let immut_ref = chinese_words.borrow();
}
二、内部可变性(Interior Mutability)模式
核心概念:允许在 “外部不可变” 的前提下修改 “内部数据”,通过运行时借用检查替代编译时检查,保证内存安全。
1. 模式原理
Rust 编译时遵循 “借用规则”:
- 同一时间,要么多个不可变引用,要么一个可变引用;
- 引用必须有效。
内部可变性通过 UnsafeCell<T>
(底层核心类型)绕开编译时检查,再通过安全封装(如 RefCell
、Cell
)提供运行时检查,确保规则不被打破。
2. 常用类型对比
类型 | 适用场景 | 数据访问方式 | 线程安全? |
---|---|---|---|
RefCell<T> | 单线程,需引用语义(如 Vec) | borrow() /borrow_mut() | ❌ |
Cell<T> | 单线程,需复制语义(如 i32) | get() /set() | ❌ |
Mutex<T> | 多线程,需互斥访问 | lock() | ✅ |
RwLock<T> | 多线程,读多写少场景 | read() /write() | ✅ |
3. 范例:用 Cell 实现单线程计数器
use std::cell::Cell;
// 计数器:外部不可变,但内部可修改
struct ChineseCounter {
count: Cell<u32>, // Cell 包裹计数(复制语义)
name: String, // 名称(不可变)
}
impl ChineseCounter {
// 新建计数器
fn new(name: &str) -> Self {
Self {
count: Cell::new(0),
name: name.to_string(),
}
}
// 增加计数(无需 &mut self)
fn increment(&self) {
let current = self.count.get();
self.count.set(current + 1);
}
// 获取计数
fn get_count(&self) -> u32 {
self.count.get()
}
}
fn main() {
let counter = ChineseCounter::new("点击计数器");
// 多次调用 increment(无需可变引用)
counter.increment();
counter.increment();
counter.increment();
println!("{}:{} 次", counter.name, counter.get_count()); // 输出:中文点击计数器:3 次
}
三、宏编程基础
宏是 Rust 的 “代码生成器”,能在编译阶段根据规则生成代码,解决 “重复代码” 和 “自定义语法扩展” 问题,分为声明宏和过程宏。
1. 声明宏(macro_rules!):模式匹配式宏
核心思想:通过 “模式匹配” 识别代码结构,替换为预定义的代码片段,语法类似 match
表达式。
语法结构:
macro_rules! 宏名称 {
// 匹配规则:(模式) => { 替换代码 }
(匹配模式1) => { 生成代码1 };
(匹配模式2) => { 生成代码2 };
// ... 更多规则
}
常用元变量:
$x
:匹配单个 token;$x:expr
:匹配表达式;$x:ident
:匹配标识符(变量名、函数名);$($x:expr),*
:匹配 0 个或多个表达式(逗号分隔)。
范例:日志打印宏
macro_rules! chinese_log {
// 规则1:info 级别
(info, $msg:expr) => {
println!("[信息] {}", $msg);
};
// 规则2:warn 级别
(warn, $msg:expr, $detail:expr) => {
println!("[警告] {} - 详情:{}", $msg, $detail);
};
// 规则3:error 级别(使用逗号分隔表达式和类型)
(error, $msg:expr, $code:expr, $ty:ty) => {
println!("[错误] {} - 错误码:{}", $msg, $code as $ty);
};
}
fn main() {
chinese_log!(info, "程序启动成功,语言:简体中文");
chinese_log!(warn, "配置文件未找到", "将使用默认配置");
// 注意这里使用逗号分隔错误码和类型
chinese_log!(error, "数据库连接失败", 500, u32);
}
2. 过程宏(Proc Macro):函数式宏
核心思想:将宏视为 “函数”,输入是 Rust 代码的抽象语法树(AST),输出是修改后的 AST,功能比声明宏更强大。
三种类型:
类型 | 用途 | 示例标注 |
---|---|---|
派生宏(Derive) | 为结构体 / 枚举自动实现 Trait | #[derive(MyTrait)] |
属性宏(Attribute) | 为代码元素(函数 / 结构体)添加自定义属性 | #[my_attr(参数)] |
函数式宏(Function-like) | 类似声明宏,支持更复杂的输入处理 | my_macro!(参数) |
(1):自定义派生宏 DeriveChineseDisplay
实现一个宏,为结构体自动生成 chinese_display()
方法,打印中文格式的字段。
需先添加依赖(Cargo.toml):
[lib]
proc-macro = true # 标记为过程宏库
[dependencies]
proc-macro2 = "1.0"
quote = "1.0" # 生成 Rust 代码
syn = "2.0" # 解析 Rust 代码为 AST
宏实现(src/lib.rs):
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Fields};
// 派生宏入口:#[derive(ChineseDisplay)]
#[proc_macro_derive(ChineseDisplay)]
pub fn derive_chinese_display(input: TokenStream) -> TokenStream {
// 1. 解析输入为 AST(结构体/枚举)
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident; // 结构体名称
// 2. 匹配结构体字段(仅处理普通结构体,忽略枚举)
let fields = match input.data {
syn::Data::Struct(s) => s.fields,
_ => panic!("仅支持结构体使用 #[derive(ChineseDisplay)]"),
};
// 3. 生成字段打印代码:例如 "名称:{},年龄:{}"
let field_print = match fields {
Fields::Named(named) => {
// 遍历命名字段(如 name: String, age: u32)
let field_parts = named.named.iter().map(|field| {
let field_name = &field.ident; // 字段名(如 name)
let field_str = field_name.as_ref().unwrap().to_string(); // 转为字符串
// 生成片段:format!("{}:{},", stringify!(#field_name), self.#field_name)
quote! {
format!("{}:{},", #field_str, self.#field_name)
}
});
// 拼接所有字段片段
quote! {
let mut output = String::new();
#(output.push_str(&(#field_parts));)*
output.pop(); // 移除最后一个逗号
output.pop();
output
}
}
_ => panic!("仅支持命名字段结构体(如 struct A { name: String })"),
};
// 4. 生成最终代码:为结构体实现 ChineseDisplay Trait
let expanded = quote! {
trait ChineseDisplay {
fn chinese_display(&self) -> String;
}
impl ChineseDisplay for #struct_name {
fn chinese_display(&self) -> String {
#field_print
}
}
};
// 5. 将生成的代码转为 TokenStream 返回
TokenStream::from(expanded)
}
宏使用(src/main.rs):
// 引入自定义派生宏
use chinese_display_macro::ChineseDisplay;
// 使用宏自动实现 ChineseDisplay Trait
#[derive(ChineseDisplay)]
struct User {
name: String, // 姓名(中文字段名)
age: u32, // 年龄
address: String, // 地址
}
fn main() {
let user = User {
name: "张三".to_string(),
age: 30,
address: "北京市朝阳区".to_string(),
};
// 调用宏生成的方法
println!("用户信息:{}", user.chinese_display());
// 输出:用户信息:name:张三,age:30,address:北京市朝阳区
}
(2):属性宏 #[chinese_comment]
为函数添加中文注释,并自动打印注释内容。
实现(src/lib.rs 新增)
use syn::{parse_macro_input, AttributeArgs, ItemFn, Lit, NestedMeta};
// 属性宏入口:#[chinese_comment("注释内容")]
#[proc_macro_attribute]
pub fn chinese_comment(attr: TokenStream, item: TokenStream) -> TokenStream {
// 1. 解析属性参数(如 "这是一个中文函数")
let args = parse_macro_input!(attr as AttributeArgs);
let comment = match &args[0] {
NestedMeta::Lit(Lit::Str(s)) => s.value(), // 提取字符串字面量
_ => panic!("#[chinese_comment] 需传入字符串参数,如 #[chinese_comment(\"中文注释\")]"),
};
// 2. 解析函数体
let item = parse_macro_input!(item as ItemFn);
let fn_name = item.sig.ident; // 函数名
let fn_block = item.block; // 函数体
// 3. 生成代码:在函数开头添加打印注释的语句
let expanded = quote! {
fn #fn_name() #fn_block {
println!("函数 {} 注释:{}", stringify!(#fn_name), #comment);
#fn_block
}
};
TokenStream::from(expanded)
}
使用(src/main.rs 新增):
use chinese_display_macro::chinese_comment;
// 为函数添加中文注释属性
#[chinese_comment("处理用户登录请求,返回是否成功")]
fn handle_login() -> bool {
println!("执行登录逻辑...");
true // 模拟登录成功
}
fn main() {
let result = handle_login();
println!("登录结果:{}", if result { "成功" } else { "失败" });
// 输出:
// 函数 handle_login 注释:处理用户登录请求,返回是否成功
// 执行登录逻辑...
// 登录结果:成功
}
四、宏的实际应用
宏的核心价值是 “消除重复代码” 和 “扩展 Rust 语法”,以下为典型应用场景。
1. 简化重复代码:数据库 CRUD 宏
假设需为多个中文表(如 user
、order
)实现 CRUD 操作,用宏避免重复编写相似代码。
范例:生成中文表的查询方法
// 声明宏:为指定表生成 "查询所有记录" 的方法
macro_rules! generate_find_all {
// 匹配:generate_find_all!(表名, 结构体名)
($table:ident, $struct:ident) => {
impl $struct {
/// 查询指定表的所有记录(中文注释由宏生成)
pub fn find_all() -> Vec<Self> {
println!("执行 SQL:SELECT * FROM `{}`", stringify!($table));
// 模拟数据库查询结果(返回 2 条测试数据)
vec![
Self::default(),
Self::default(),
]
}
}
// 为结构体实现默认值(简化示例)
impl Default for $struct {
fn default() -> Self {
Self {
id: 0,
name: "默认名称".to_string(),
}
}
}
};
}
// 1. 定义用户表结构体
struct UserTable {
id: u32,
name: String,
}
// 2. 调用宏生成 find_all 方法
generate_find_all!(user, UserTable);
// 3. 定义订单表结构体
struct OrderTable {
id: u32,
name: String, // 订单名称(如“20240822-张三订单”)
}
// 4. 调用宏生成 find_all 方法
generate_find_all!(order, OrderTable);
fn main() {
// 使用宏生成的方法查询数据
let users = UserTable::find_all();
let orders = OrderTable::find_all();
println!("查询到用户数量:{}", users.len()); // 输出:查询到用户数量:2
println!("查询到订单数量:{}", orders.len()); // 输出:查询到订单数量:2
}
2. 自定义 Derive:简化序列化 / 反序列化
Rust 标准库的 serde
框架就是通过自定义派生宏 #[derive(Serialize, Deserialize)]
实现自动序列化,以下为简化版示例。
范例:DeriveJson 宏(自动生成 JSON 字符串方法)
// 依赖同前(proc-macro2、quote、syn)
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Fields};
#[proc_macro_derive(DeriveJson)]
pub fn derive_json(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident;
let fields = match input.data {
syn::Data::Struct(s) => s.fields,
_ => panic!("仅支持结构体"),
};
// 生成 JSON 键值对:"name":"张三", "age":30
let json_parts = match fields {
Fields::Named(named) => named.named.iter().map(|field| {
let field_name = &field.ident;
let field_str = field_name.as_ref().unwrap().to_string();
quote! {
format!("\"{}\":\"{}\"", #field_str, self.#field_name)
}
}),
_ => panic!("仅支持命名字段"),
};
let expanded = quote! {
trait ToJson {
fn to_json(&self) -> String;
}
impl ToJson for #struct_name {
fn to_json(&self) -> String {
let parts = vec![#(#json_parts),*];
format!("{{{}}}", parts.join(", "))
}
}
};
TokenStream::from(expanded)
}
使用:
use my_json_macro::DeriveJson;
#[derive(DeriveJson)]
struct Product {
product_name: String, // 商品名称(中文)
price: f64, // 价格
category: String, // 分类(如“电子产品”)
}
fn main() {
let phone = Product {
product_name: "智能手机".to_string(),
price: 3999.99,
category: "电子产品".to_string(),
};
println!("商品 JSON:{}", phone.to_json());
// 输出:商品 JSON:{"product_name":"智能手机", "price":"3999.99", "category":"电子产品"}
}
五、unsafe Rust
Rust 的 unsafe
关键字用于 “opt-out” 安全检查,允许直接操作内存,但需开发者自行保证安全。unsafe
不意味着 “不安全”,而是 “编译器无法验证安全性”。
1. unsafe 块:标记不安全操作
可在 unsafe 块中执行的操作:
- 解引用原始指针(
*const T
/*mut T
); - 调用
unsafe
函数或方法; - 访问或修改
static mut
变量; - 实现
unsafe
Trait。
范例:解引用原始指针
fn main() {
// 使用纯ASCII字符串(避免多字节UTF-8编码被破坏)
let mut ascii_str = "Hello, Rust".to_string();
// 1. 获取原始指针,变量名前加_消除未使用警告
let ptr: *mut u8 = ascii_str.as_mut_ptr();
let _len: usize = ascii_str.len(); // 加_前缀表示有意未使用
// 2. 在unsafe块中修改第一个字节(ASCII字符单字节,不会破坏UTF-8)
unsafe {
*ptr = b'N'; // 将'H'改为'N',结果为"Nello, Rust"(有效UTF-8)
}
// 3. 安全打印(此时字符串仍是有效的UTF-8)
println!("修改后的字符串:{}", ascii_str); // 输出:修改后的字符串:Nello, Rust
}
2. 原始指针(Raw Pointers)
类型:
*const T
:不可变原始指针,仅允许读操作;*mut T
:可变原始指针,允许读 / 写操作。
特点:
- 不实现
Deref
Trait,需显式解引用(*ptr
); - 不保证指向有效内存;
- 允许空指针、悬垂指针;
- 不遵循借用规则(可同时存在多个可变指针)。
范例:原始指针传递数据
fn main() {
let mut numbers = [10, 20, 30]; // 中文场景:假设存储中文编码对应的数值
// 1. 获取数组的原始指针和长度
let ptr: *mut i32 = numbers.as_mut_ptr();
let len = numbers.len();
// 2. 调用 unsafe 函数处理指针
unsafe {
modify_array(ptr, len);
}
// 3. 打印修改后的数组
println!("修改后的数组:{:?}", numbers); // 输出:修改后的数组:[11, 21, 31]
}
// unsafe 函数:接收原始指针,修改数组元素(需开发者保证指针有效)
unsafe fn modify_array(ptr: *mut i32, len: usize) {
for i in 0..len {
// 计算第 i 个元素的指针(offset 接收字节数,i32 占 4 字节,故 offset(i as isize))
let elem_ptr = ptr.offset(i as isize);
// 解引用并修改(安全前提:elem_ptr 指向有效内存)
*elem_ptr += 1;
}
}
3. 外部函数接口(FFI):调用 C 代码
FFI(Foreign Function Interface)允许 Rust 调用其他语言(如 C)的函数,因其他语言不保证 Rust 的安全规则,需用 unsafe
包裹。
范例:Rust 调用 C 的中文字符串处理函数
步骤 1:编写 C 代码(src/hello.c)
#include <string.h>
// C 函数:将中文字符串转换为大写(简化示例,实际需处理 UTF-8)
void to_upper(char *str) {
for (int i = 0; str[i] != '\0'; i++) {
// 仅处理 ASCII 字符(中文 UTF-8 编码需更复杂逻辑)
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] -= 32;
}
}
}
步骤 2:配置 Cargo.toml(编译 C 代码)
[build-dependencies]
cc = "1.0" # 用于编译 C 代码
步骤 3:编写构建脚本(build.rs)
// 构建脚本:编译 C 代码为静态库
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello"); // 生成 libhello.a
}
步骤 4:Rust 调用 C 函数(src/main.rs)
// 1. 声明 C 函数(告知 Rust 函数签名)
extern "C" {
// C 函数:void to_upper(char *str)
fn to_upper(str: *mut u8);
}
fn main() {
// 2. 准备中文字符串(注意:C 字符串需以 \0 结尾)
let mut chinese_str = b"hello, 中文测试\0".to_vec(); // 字节数组,末尾加 \0
// 3. 调用 C 函数(unsafe 块,因 C 不保证内存安全)
unsafe {
to_upper(chinese_str.as_mut_ptr()); // 传递可变原始指针
}
// 4. 转换为 Rust 字符串并打印(忽略 \0 结尾)
let result = String::from_utf8_lossy(&chinese_str[..chinese_str.len()-1]);
println!("C 函数处理结果:{}", result); // 输出:C 函数处理结果:HELLO, 中文测试
}
4. unsafe 安全原则
使用 unsafe
时必须遵守以下原则,避免内存安全问题:
- 指针有效性:解引用前确保指针指向有效、未释放的内存;
- 借用规则:即使使用原始指针,也需避免数据竞争(如多线程同时写);
- 生命周期:确保指针的生命周期不超过指向数据的生命周期;
- 文档说明:为
unsafe
函数 / 块编写详细文档,说明安全前提。