Rust 进阶特性与宏编程

一、智能指针(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>(底层核心类型)绕开编译时检查,再通过安全封装(如 RefCellCell)提供运行时检查,确保规则不被打破。

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 宏

假设需为多个中文表(如 userorder)实现 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 时必须遵守以下原则,避免内存安全问题:

  1. 指针有效性:解引用前确保指针指向有效、未释放的内存;
  2. 借用规则:即使使用原始指针,也需避免数据竞争(如多线程同时写);
  3. 生命周期:确保指针的生命周期不超过指向数据的生命周期;
  4. 文档说明:为 unsafe 函数 / 块编写详细文档,说明安全前提。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值