首页 > 其他分享 >精通rust宏系列教程-入门篇

精通rust宏系列教程-入门篇

时间:2024-11-15 13:50:43浏览次数:3  
标签:教程 匹配 name macro 表达式 入门篇 token rust Rust

Rust最令人敬畏和强大的特性之一是它使用和创建宏的能力。不幸的是,用于创建宏的语法可能相当令人生畏,并且对于新开发人员来说,这些示例可能会令人不知所措。我向你保证Rust宏非常容易理解,本文将为你介绍如何创建自己的宏。

什么是Rust宏?

println!("hello {}", name)

如果你已经尝试过Rust,你应该已经使用过一个宏println!这个宏允许您打印一行文本,并能够在文本字符串中插入变量。

宏只是允许你发明自己的语法并编写代码生成更多的代码。这被称为元编程,支持语法糖,使你的代码更短,更容易使用你的库,甚至可以在rust中创建自己的DSL(领域特定语言)。
在这里插入图片描述

宏的工作方式是匹配宏规则中定义的特定模式,将匹配的部分捕获为变量,然后展开以生成更多代码。如果你听不懂也没关系。让我们开始吧!

如何创建宏

我们可以使用macro_rules!宏。Macroception !

这就是如何创建一个空白嘿!宏;很明显,它现在没有任何作用。

macro_rules! hello {
	() => {}
}

()=>{}部分似乎很有趣,不是吗?

这是一个宏规则的条目,我们可以在一个宏中有许多规则来匹配它。这与模式匹配非常相似,在模式匹配中,我们也可以有许多用逗号分隔的情况。

但是,它到底是什么意思呢?

()=>{}
匹配器编写器
匹配模式扩展代码

括号部分是匹配器,它将允许我们匹配模式并捕获其中一部分作为变量。这就是我们如何发明自己的自定义语法和dsl的方法。

花括号部分是转录器,在这里我们可以使用从匹配器捕获的变量。Rust编译器会将宏的代码及其变量扩展为实际的Rust代码。

匹配模式

我们如何匹配一个模式呢?让我们来看看。

($name:expr)
  • $name 将在后面编写器引用
  • 指示符:用于匹配类型,如:expression(表达式类型)

Rust将尝试匹配在匹配器对中定义的模式,匹配器对可以是()、{}或[]。宏规则中的字符将与输入进行匹配,以确定是否匹配。在规则的模式匹配成功之后,我们可以捕获模式的一部分,并将其捕获为一个变量,以便在编写器中使用。

美元符号之后的部分是变量名称,它将在编写器中作为变量使用,这就是我们的代码所在的位置。在本例中,我们当前将其捕获为$name。

冒号后面的部分称为指示符,是我们可以选择匹配的类型。例如,我们目前使用的是表达式指示符,由冒号后面的expr表示。这告诉Rust匹配一个表达式,并将其捕获为$name。

我们可以使用许多指示符,而不仅仅是表达式。下面是Rust中可用的指示符的快速列表:

  • 标识符(Identifiers)

说明:标识符是用来表示变量、函数、结构体等的名称。在声明性宏中,标识符参数用于匹配和捕获代码中的名称,然后可以在宏展开时对这些名称进行操作。

macro_rules! print_variable_name {
    ($var:ident) => {
        println!("The variable name is: {}", stringify!($var));
    };
}
let x = 5;
print_variable_name!(x);
  • 字面量(Literals)

说明:字面量包括整数字面量、浮点数字面量、字符字面量和字符串字面量等。在宏中,字面量参数用于匹配特定类型的常量值。

-- 数值字面量
macro_rules! double_number {
    ($num:literal) => {
        $num * 2
    };
}
let result = double_number!(5);
assert_eq!(result, 10);

-- 字符串字面量
macro_rules! print_greeting {
    ($name:literal) => {
        println!("Hello, {}!", $name);
    };
}
print_greeting!("Alice");
  • 路径(Paths)

说明:路径用于指定一个类型、函数或者模块的位置。在宏中,路径参数可以用来匹配和操作代码中的类型或函数引用。

macro_rules! call_function {
    ($func_path:path) => {
        $func_path();
    };
}
fn my_function() {
    println!("This function is called through a macro.");
}
call_function!(my_function);

$func_path:path是路径参数。这里宏call_function接受函数路径作为参数,然后调用该函数。

  • 表达式(Expressions)

说明:表达式是可以计算出值的代码片段。在宏中,表达式参数用于匹配和操作代码中的各种表达式,如算术表达式、函数调用表达式等。

macro_rules! square_expression {
    ($expr:expr) => {
        ($expr) * ($expr)
    };
}
let x = 3;
let result = square_expression!(x + 1);
assert_eq!(result, 16);

square_expression接受一个表达式作为参数,然后将这个表达式自身相乘,计算出表达式值的平方。

  • 类型(Types)

说明:类型参数用于匹配和操作代码中的数据类型。可以在宏中根据不同的类型进行不同的代码展开。

macro_rules! print_type_name {
    ($ty:ty) => {
        println!("The type is: {}", stringify!($ty));
    };
}
print_type_name!(u32);

$ty:ty是类型参数。宏print_type_name接受类型作为参数,然后打印出这个类型的名称(通过stringify!宏转换为字符串)

  • 块(Blocks)

说明:块是由花括号包围的一系列语句。在宏中,块参数用于匹配和操作代码中的语句块,这样可以在宏展开时对整个语句块进行处理。

macro_rules! execute_block {
    ($block:block) => {
        $block
    };
}
let result = execute_block!({
    let x = 5;
    x * 2
});
assert_eq!(result, 10);

execute_block接受语句块作为参数,然后执行这个语句块并返回结果。

  • token(标记)参数

token参数是一种比较通用的参数类型,它可以匹配几乎任何语法结构单元。这包括但不限于关键字、操作符、标点符号等。使用token参数可以让宏更灵活地处理各种复杂的语法模式。

macro_rules! handle_tokens {
    ($first_token:token $second_token:token) => {
        println!("The first token is: {:?}, the second token is: {:?}", $first_token, $second_token);
    };
}
handle_tokens!(let +);

$first_token:token$second_token:token用于匹配任意两个连续的语法标记。这个宏会打印出这两个标记的内容。

  • meta(元数据)参数

meta参数用于匹配和处理属性(attribute)相关的语法结构。在 Rust 中,属性用于为各种语言元素(如函数、结构体、枚举等)添加额外的元数据,如条件编译信息、派生(derive)的 trait 等。

macro_rules! print_meta {
    ($meta:meta) => {
        println!("The meta data is: {:?}", $meta);
    };
}
#[derive(Debug)]
struct MyStruct;
print_meta!(#[derive(Debug)]);

$meta:meta用于匹配属性语法结构。这个宏可以打印出属性的内容,就像上面打印#[derive(Debug)]这个属性一样。

第一个宏示例

太酷了! 现在我们知道了创建简单宏的足够内容。让我们创造属于我们自己的hello!宏。

macro_rules! hello{
	($name:expr) => {
        println!("hello {}!", $name);
    };
}

fn main(){
    hello!("Rust");
}

就是这样,我们刚刚创建了第一个宏!这个程序会打印出 hello Rust!作为调用宏的结果。我们匹配了输入表达式,并将其捕获为$name变量,然后在编写器中使用捕获的$name,它将由Rust编译器展开。这看起来很简单,不是吗?

第二个宏示例

我们所熟悉和喜爱的许多宏可以一次接受大量输入。vec!宏就是典型例子;我们可以通过调用vec!宏像这样:vec![1,2,3,4,5]。vec!宏如何实现的呢,我们来慢慢分解说明。

( $( $x:expr),* )

这些*号 会在$(...) 重复多次, 逗号是分隔符; 

我们只需将想要重复的模式放在$(…)部分中。然后,插入分隔符,在本例中是逗号(,)符号。这将是一个字符,将模式分开,让我们有重复。

最后在末尾添加星号(*)符号,它将重复匹配$()中的模式,直到匹配完成为止。困惑吗?让我们再看一个例子吧!

在这种情况下,hello!宏,我们捕捉所有的输入表达式作为$name,我们将继续匹配它,直到没有匹配。我们可以用任意多的参数调用这个宏。

仍然困惑吗?这完全没问题!让我们实现一个令人敬畏的现实世界的宏,看看它是如何工作的。

  • 实现Rust HashMap
use std::collections::HashMap;

macro_rules! mapx {
    ( $( $key:expr=>$value:expr ),* ) => {
        {
            let mut hmap = HashMap::new();
            $(hmap.insert($key, $value);)*
            hmap
        }
    };
}

fn main() {
    let addr_maps = mapx!(
        "a1"=>"beijing01", 
        "a2"=>"beijing02",
        "a3"=>"beijing03"
);
    println!("{:?}", addr_maps);
}
// 输出结果:
// {"a1": "beijing01", "a2": "beijing02", "a3": "beijing03"}
  • 定义了宏 mapx!,它接受一系列以 => 分隔的键值对表达式作为参数。
  • 在宏展开时,首先创建一个新的空 HashMap,然后通过循环遍历传入的键值对,将每个键值对插入到 HashMap 中,最后返回初始化好的 HashMap

这样就可以方便地一次性初始化多个 HashMap 的键值对,类似于 vec! 宏用于初始化向量的便捷方式。

这里需要注意的是,这段展开代码使用 {} 括起来, 虽然外面已经有 {};,下面我们分析下:

        {
            let mut hmap = HashMap::new();
            $(hmap.insert($key, $value);)*
            hmap
        }
  1. 作用域和变量遮蔽问题
    • 在宏展开的环境中,使用{}创建一个新的内部块作用域是很重要的。虽然外层可能有其他的块作用域(比如main函数本身就是一个块作用域),但这里的内部块主要是为了确保变量的正确生命周期和作用范围。
    • 如果没有这个内部块,在宏展开时可能会出现变量遮蔽(shadowing)等问题。例如,如果在宏调用的上下文中已经存在一个名为hmap的变量,那么没有内部块的话,宏内部对hmap的操作可能会意外地影响到外部的hmap变量。
  2. 返回值的明确性
    • 这个内部块最后返回hmap,明确了宏的返回值是这个新创建并初始化好的HashMap。从语法角度看,在 Rust 的表达式导向的语法中,一个块({})可以作为一个表达式,它的值是最后一个表达式的值(在这里就是hmap的值)。如果没有这个块,宏展开后的代码在语法上可能不符合期望的返回值要求,导致编译错误或者意外的行为。

最后总结

本文介绍了Rust宏,主要了解宏的宏的工作方式,如何匹配宏规则中定义的特定模式,将匹配的部分捕获为变量,然后展开以生成更多代码。我们通过几个简单示例,你应该大概清楚了创建宏的过程。当然要掌握Rust宏,还有很长的路要走,如何编写声明宏、过程宏等,未来我们继续,一起rust。

标签:教程,匹配,name,macro,表达式,入门篇,token,rust,Rust
From: https://blog.csdn.net/neweastsun/article/details/143780664

相关文章

  • springBoot-RabbitMQ 高级特性(保姆级教程,一步一步带你熟悉RabbitMQ 相关高级特性)
    话不多说,看项目整体架构RabbitMQ高级特性保姆级教程好了,下面县开始贴生产者代码:publisher父依赖:<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.7.18</versi......
  • Python小白学习教程从入门到入坑------第三十二课 生成器(语法进阶)
    目录一、生成器generator1.1生成器表达式1.1.1表达式一1.1.2表达式二二、可迭代对象、迭代器、生成器三者之间的关系2.1定义与特性2.2关系与区别一、生成器generator在Python中,生成器(Generators)是一种用于迭代对象的特殊类型函数。它们允许你生成一个序列......
  • Python小白学习教程从入门到入坑------第三十一课 迭代器(语法进阶)
    目录一、可迭代对象Iterable1.1可迭代对象的条件1.2for循环工作原理1.3isinstance()二、迭代器 Iterator2.1 __iter__() 和 __next__()2.2 可迭代对象&迭代器2.2.1定义与特性2.2.2 关系与转换2.2.3应用场景三、迭代器协议(了解即可)四、自定义迭代器类......
  • 【stable diffusion模型】Stability AI出官方教程了,带你轻松玩转Stable Diffusion 3.5
    前言提示(prompt)是有效使用生成式AI图像模型的关键技巧。提示的结构直接影响生成的图像的质量、创造力和准确性。今日凌晨,StabilityAI发布了StableDiffusion3.5的提示指南。该指南提供了StableDiffusion3.5的实用提示技巧,让使用者能够快速准确地完善图像概念,......
  • CTF入门教程(非常详细)从零基础入门到竞赛,看这一篇就够了!
    文章目录CTF基础知识一、CTF简介二、CTF赛事介绍三、CTF竞赛模式1.解题模式(Jeopardy)2.攻防模式(Attack-Defense)3.混合模式(Mix)四、CTF竞赛内容国内外著名赛事1、国际知名CTF赛事2、国内知名CTF赛事五、如何学习CTF1、分析赛题2、常规操作3、入门知识推荐书籍零基础网......
  • 2024爆火全网LLM大模型书籍:从零构建大型语言模型,重磅开源教程!!标星20.3K
    自ChatGPT发布以来,大型语言模型(LLM)已经成为推动人工智能发展的关键技术。近期,机器学习和AI研究员、畅销书《Python机器学习》作者SebastianRaschka又写了一本新书——《BuildaLargeLanguageModel(FromScratch)》,旨在讲解从头开始构建大型语言模型的整个过程......
  • 强大的cad绘图软件推荐:AutoCAD 2023中文 AutoCAD 2023激活教程
    AutoCAD2023是由美国Autodesk公司推出的一款计算机辅助设计软件,广泛应用于建筑、机械、航空、电子和地理信息系统等多个领域。这款软件具有强大的二维和三维设计、制图和分析功能,可以帮助用户轻松创建各种复杂的模型。在功能上,AutoCAD2023提供了平面绘图、编辑图形、三维绘......
  • 【Civit3D 2025下载与安装教程】
    1、安装包「Civil3d_2025」:链接:https://pan.quark.cn/s/b05281a72f24提取码:PUWx「Civil3D2020」:链接:https://pan.quark.cn/s/61c01d7bd533提取码:RbML2、安装教程(建议关闭杀毒软件)1)       双击Setup.exe安装,弹窗安装对话框  2)       勾选‘我同意......
  • rust逆向初探
    rust逆向葵花宝典rust逆向技巧rust逆向三板斧:[!NOTE]快速定位关键函数(真正的main函数):观察输出、输入,字符串搜索,断点等方法。定位关键加密区:根据输入的flag,打硬件断点,快速捕获程序中对flag访问的位置(加密区)。定位错误输出(附近一定有比较功能的程序):定位到比较位置......
  • 【教程】第七章:工作流——自动赋能,效率飞跃
    恭喜你走到了这最后一章!我们将在这一章中介绍和简单探索NocoBase的强大工作流功能。通过这个功能,你可以为系统中的任务自动化操作,节省时间并提升效率。上节挑战答案但在开始之前,先回顾一下上节的挑战吧!我们成功地为“伙伴”角色配置了评论权限,如下:添加权限:允许用户发布评论......