rust中的宏

macro.jpeg

宏的核心概念:是什么,有什么用

首先,先帮你建立基础认知:

  • 宏是什么:宏是Rust的一种"元编程"工具,它不是在运行时执行,而是在编译期展开成普通的Rust代码。你可以把它理解成"代码生成器",接收代码片段作为输入,输出新的代码。
  • 宏和函数的区别:函数要求参数类型固定、数量固定;而宏可以接收任意类型、任意数量的参数,还能操作代码结构(比如生成重复的代码块)。
  • 常见用途:简化重复代码(比如vec![])、实现DSL(领域特定语言)、做编译期检查等。
    Rust主要有两类宏:
  1. 声明式宏(Declarative Macros):用macro_rules!定义,最常用、最易上手。
  2. 过程宏(Procedural Macros):更强大但复杂,分为派生宏(如#[derive(Debug)])、属性宏、函数式宏。

一、最核心的基础语法:声明式宏(macro_rules!)

声明式宏是新手最先接触、最易掌握的,先把它的基础语法啃透,过程宏的基础语法后面再补充。

1. 定义语法(最核心模板)

声明式宏的定义有固定模板,拆成3部分看就懂了:

// 完整定义模板(逐部分解释)
macro_rules! 宏名称 { // 1. 宏定义关键字 + 宏名(命名规范:小写,和普通标识符规则一致)
 (匹配模式) => { // 2. 匹配模式:定义宏被调用时的格式;=> 是固定分隔符
 展开的代码块 // 3. 展开代码:宏被调用后,实际生成的Rust代码
 }; // 分号结束这一组匹配规则(可以有多组匹配规则)
 // 可以加更多匹配规则(处理不同调用格式)
 (另一组匹配模式) => {
 另一组展开代码
 };
}

最简示例(无参数宏):先写一个连参数都没有的宏,理解最基础的定义和调用

// 定义:最简单的宏,无参数
macro_rules! hello {
 // 匹配模式:() 表示调用时不带任何参数
 () => {
 // 展开代码:宏调用后,这里的代码会被替换到调用位置
 println!("Hello, Rust宏!");
 };
}
// 调用:宏名 + ! + 匹配模式里的内容(这里是())
fn main() {
 hello!(); // 调用语法:宏名! + (匹配的内容),输出:Hello, Rust宏!
}

2. 调用语法(核心规则)

宏的调用语法只有一个核心规则:

调用格式 = 宏名 + ! + 匹配模式里的内容
!是宏和普通函数的核心区别,必须加)

举几个不同匹配模式对应的调用例子,一看就懂:

宏定义的匹配模式 调用语法(正确) 调用语法(错误) 说明
() hello!() hello()(少!) 无参数调用
($x:expr) say!(123) say!(123, 456)(参数多了) 单参数调用
($x:expr, $y:expr) add!(1, 2) add!("a")(参数少了) 双参数调用
($($x:expr),*) vec_like![1,2,3] vec_like![1;3](分隔符错了) 可变参数调用

3. 带参数的基础宏(新手最常用)

用具体例子拆解“参数怎么定义”“怎么调用”,这是新手最需要的:

例子1:单参数宏(匹配单个表达式)
// 定义:带1个参数的宏(参数是任意表达式)
macro_rules! print_one {
 // 匹配模式:($x:expr) 表示接收1个表达式参数,命名为$x
 ($x:expr) => {
 // 展开代码:使用参数$x
 println!("你传入的参数是:{:?}", $x);
 };
}
// 调用:按匹配模式传1个表达式
fn main() {
 print_one!(100); // 传入数字:输出 你传入的参数是:100
 print_one!("hello"); // 传入字符串:输出 你传入的参数是:"hello"
 print_one!(1+2*3); // 传入表达式:输出 你传入的参数是:7
}
例子2:多参数宏(固定数量参数)
// 定义:带2个参数的宏(两个都是表达式)
macro_rules! print_two {
 // 匹配模式:($x:expr, $y:expr) 接收2个表达式,用逗号分隔
 ($x:expr, $y:expr) => {
 println!("参数1:{:?},参数2:{:?}", $x, $y);
 };
}
// 调用:传2个参数,用逗号分隔
fn main() {
 print_two!(10, "rust"); // 正确调用:输出 参数1:10,参数2:"rust"
 // print_two!(10); // 错误:参数数量不匹配,编译报错
}
例子3:可变参数宏(任意数量参数)

这是新手最容易懵的“重复模式”,用最简单的例子讲:

// 定义:接收任意数量参数的宏
macro_rules! print_many {
 // 匹配模式:$( $x:expr ),* 
// $() 包裹要重复的参数;* 表示0次/多次;, 是参数间的分隔符
 ($( $x:expr ),*) => {
 println!("开始打印所有参数:");
 // 展开代码也用 $(...)* 重复:每个参数打印一次
 $(
 println!("参数:{:?}", $x);
 )*
 };
}
// 调用:可以传0个、1个、多个参数
fn main() {
 print_many!(); // 0个参数:只输出“开始打印所有参数:”
 print_many!(100); // 1个参数:打印1行
 print_many!(1, "a", 3.14); // 3个参数:打印3行
}

4. 关键语法符号拆解(新手必记)

符号/关键字 作用 例子
macro_rules! 声明式宏的定义关键字(固定开头) macro_rules! my_macro { ... }
! 调用宏的标志(区分宏和函数) my_macro!()
=> 分隔“匹配模式”和“展开代码”(固定) () => { println!("hi"); };
$ 标记宏的参数(参数必须以$开头) $x:expr(参数名x,类型是表达式)
: 分隔参数名和“片段说明符”(指定参数类型) $name:ident(参数name是标识符)
$(...)* 重复模式:匹配/展开任意次数(0+) $( $x:expr ),*(匹配多个表达式)
$(...)+ 重复模式:匹配/展开至少1次 $( $x:expr ),+(必须传至少1个参数)

二、过程宏的基础语法(新手入门级)

过程宏的定义/调用语法和声明式宏完全不同,先记“怎么用”,再懂“怎么定义”:

1. 派生宏(最常用的过程宏):调用+定义基础

(1)调用语法(新手先会用)

派生宏是加在结构体/枚举上的属性,格式:

// 调用语法:#[derive(宏名)] 加在类型定义上方
#[derive(Debug, Clone, 自定义宏名)] // 可以多个派生宏一起用
struct User {
 name: String,
 age: u32,
}
(2)定义语法(新手了解即可)

过程宏必须定义在库(lib) 中,且Cargo.toml要加配置,基础模板:

# Cargo.toml 必须加的配置(固定)
[lib]
proc-macro = true # 标记这是一个过程宏库
[dependencies]
# 过程宏三大基础依赖(固定)
syn = "2.0" # 解析Rust代码
quote = "1.0" # 生成Rust代码
proc-macro2 = "1.0" # 辅助处理Token
// src/lib.rs 定义派生宏的基础模板
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;
// 定义派生宏的注解(固定格式:#[proc_macro_derive(宏名)])
#[proc_macro_derive(MyDerive)]
// 函数签名固定:接收TokenStream,返回TokenStream
pub fn my_derive_macro(input: TokenStream) -> TokenStream {
 // 1. 解析输入的代码(比如结构体定义)
 let input = parse_macro_input!(input);
 // 2. 生成要展开的代码(用quote!宏)
 let output = quote! {
 // 这里写宏要生成的代码
 impl MyDerive for YourType {
 // 方法定义
 }
 };
 // 3. 转换为TokenStream返回
 TokenStream::from(output)
}

2. 属性宏(调用语法)

属性宏是自定义注解,调用语法:

// 调用语法:#[宏名(可选参数)]
#[my_attr(debug = true)] // 带参数的属性宏
fn my_function() {
 // 函数体
}
#[my_attr] // 不带参数的属性宏
struct MyStruct;

三、新手必练的基础小例子(敲一遍就会)

写这3个例子,彻底掌握基础语法:

// 例子1:无参数宏
macro_rules! greet {
 () => {
 println!("Hello, 新手!");
 };
}
// 例子2:单参数宏(打印任意类型)
macro_rules! print_val {
 ($val:expr) => {
 println!("值:{:?},类型:{}", $val, std::any::type_name_of_val(&$val));
 };
}
// 例子3:双参数宏(计算两数之和)
macro_rules! add {
 ($a:expr, $b:expr) => {
 $a + $b // 直接返回表达式结果
 };
}
fn main() {
 // 调用1:无参数
 greet!();
 // 调用2:单参数
 print_val!(123);
 print_val!("abc");
 // 调用3:双参数(可以直接赋值给变量)
 let sum = add!(10, 20);
 println!("10+20={}", sum);
}

运行结果:

Hello, 新手!
值:123,类型:i32
值:"abc",类型:&str
10+20=30

总结

  1. 声明式宏最基础的语法:定义用macro_rules! 宏名 { (匹配模式) => { 展开代码 }; },调用用宏名! + 匹配模式内容!是宏的核心标识。
  2. 宏参数必须以$开头,用:指定类型(如$x:expr表示表达式参数),$(...)*处理可变数量参数。
  3. 过程宏调用是属性形式(#[derive(宏名)]),定义需放在proc-macro库中,新手先会用即可。
    如果敲上面的例子时遇到任何报错,或者想搞懂某个具体符号的用法,都可以问,咱们一点点拆~

全文由豆包生成

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、概述 为了解决rust语法元素的扩展,并能复用现有的代码,在rust编写的程序中普遍使用宏.通过宏定义和宏调用...
    神奇的考拉阅读 4,200评论 0 2
  • Rust 的宏,这里主要指的是 macro_rules!,可以实现很多强有力的工具,但是毕竟跟函数实现还是不同,需...
    家中古词阅读 3,414评论 0 0
  • 宏允许开发者在编译时生成代码,从而减少重复工作并提升灵活性。 Rust 的宏分为两类: 声明式宏(Declarat...
    原水寒阅读 159评论 0 0
  • 1. 宏编程范式 C++语言是大量使用宏编程范式的一个典范,宏用得好,能大幅度的简化代码。在rust中,也吸收借鉴...
    Wu杰语阅读 174评论 0 0
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,663评论 0 5

友情链接更多精彩内容