精通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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915854.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式之装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)

前言: 两个本想描述一样的意思的词,只因一字只差就让人觉得一个是好牛,一个好搞笑。往往我们去开发编程写代码时也经常将一些不恰当的用法用于业务需求实现中,但却不能意识到。一方面是由于编码不多缺少较大型项目的实践&#xff…

kubernetes简单入门实战

本章将介绍如何在kubernetes集群中部署一个nginx服务,并且能够对其访问 Namespace Namespace是k8s系统中一个非常重要的资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下,k8s集群中的所有的Pod都是可以相…

webpack5 + vue3 从零配置项目

前言 虽然在实际项目当中很少会从 0 到 1 配置一个项目,毕竟很多重复工作是没有必要的,脚手架将这些重复性的工作进行了整合,方便开发者使用。也正因如此,导致部分开发者过于依赖脚手架,却不清楚其内部的实现流程&…

Linux git-bash配置

参考资料 命令提示符Windows下的Git Bash配置,提升你的终端操作体验WindowsTerminal添加git-bash 目录 一. git-bash配置1.1 解决中文乱码1.2 修改命令提示符 二. WindowsTerminal配置git-bash2.1 添加git-bash到WindowsTerminal2.2 解决删除时窗口闪烁问题 三. VS…

(RK3566驱动开发 - 1).pinctrl和gpio子系统

一.设备树 pinctrl部分可以参考 rockchip 官方的绑定文档 :kernel/Documentation/devicetree/bindings/pinctrl PIN_BANK:引脚所属的组 - 本次例程使用的是 GPIO3_A1 这个引脚,所以所属的组为 3; PIN_BANK_IDX:引脚的…

基于OpenFOAM和深度学习驱动的流体力学计算与应用

在深度学习与流体力学深度融合的背景下,科研边界不断拓展,创新成果层出不穷。从物理模型融合到复杂流动模拟,从数据驱动研究到流场智能分析,深度学习正以前所未有的力量重塑流体力学领域。近期在Nature和Science杂志上发表的深度学…

uniapp设置tabBar高斯模糊并设置tabBar高度占位

1、设置tabBar高斯模糊 2、设置tabBar高度占位 (1)需要先在App.vue中获取一下 uni.getSystemInfoSync().windowBottom; //返回值是tabBar的高度(2)在全局样式文件/uview-ui/libs/css/style.vue.scss中定义一个全局样式 3、在需…

嵌入式Linux输入系统应用编程学习总结

嵌入式Linux输入系统应用编程学习总结 目录 嵌入式Linux输入系统应用编程学习总结1. 嵌入式Linux输入系统介绍2. Linux设备输入数据的几个结构体2.1 内核中表示一个输入设备的结构体2.2 APP从输入设备获取的数据类型结构体 3. 查看LCD设备信息和输入数据3.1 查看设备信息3.2 使…

力扣=Mysql-3322- 英超积分榜排名 III(中等)

一、题目来源 3322. 英超积分榜排名 III - 力扣(LeetCode) 二、数据表结构 表:SeasonStats --------------------------- | Column Name | Type | --------------------------- | season_id | int | | team_id …

HTML之列表学习记录

练习题&#xff1a; 图所示为一个问卷调查网页&#xff0c;请制作出来。要求&#xff1a;大标题用h1标签&#xff1b;小题目用h3标签&#xff1b;前两个问题使用有序列表&#xff1b;最后一个问题使用无序列表。 代码&#xff1a; <!DOCTYPE html> <html> <he…

【ElasticSearch】定位分片不分配

记录遇到的es集群分片不分配的情况--待补全 定位分片不分配的原因 定位分片不分配的原因 在shell客户端执行如下的语句&#xff1a; curl -X GET "http://192.168.0.209:9200/_cat/shards?v&hindex,shard,prirep,state,unassigned.reason"集群中各分片的状态都…

10款PDF合并工具讲解与推荐!!!

在现在的大环境下&#xff0c;PDF文件因其跨平台、格式固定等优势&#xff0c;成为了我们工作和学习中不可或缺的一部分。是最常用的文档格式之一。然而&#xff0c;面对多个PDF文件需要合并成一个的场景&#xff0c;如何选择一款高效、易用的PDF合并工具就显得尤为重要。今天&…

「QT」窗口类 之 QWidget 窗口基类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C」C/C程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定制…

【机器学习】如何配置anaconda环境(无脑版)

马上就要上机器学习的实验&#xff0c;这里想写一下我配置机器学习的anaconda环境的二三事 一、首先&#xff0c;下载安装包&#xff1a; Download Now | Anaconda 二、打开安装包&#xff0c;一直点NEXT进行安装 这里要记住你要下载安装的路径在哪&#xff0c;后续配置环境…

【3D Slicer】的小白入门使用指南四

开源解剖影像浏览工具Open Anatomy Browser使用及介绍 和3D slicer米有太大关系,该工具是网页版影像数据的浏览工具(可以简单理解为网页版的3D slicer) 介绍 ● 开放解剖(OA)浏览器是由神经影像分析中心开发的,基于网络浏览器技术构建的图谱查看器。 ● OA浏览器将解剖模…

Unity使用PS合并贴图

前言 使用PBR渲染&#xff0c;金属工作流时&#xff0c;默认使用一个金属度贴图&#xff0c;其中r通道保存金属度&#xff0c;a通道保存光滑度&#xff0c;g通道和b通道没使用&#xff1b; 我们很可能使用Occlusion Map&#xff0c;使用其中的g通道保存Occlusion 信息。单独使用…

Linux中.NET读取excel组件,不会出现The type initializer for ‘Gdip‘ threw an exception异常

组件&#xff0c;可通过nuget安装&#xff0c;直接搜名字&#xff1a; ExcelDataReader using ConsoleAppReadFileData.Model; using ExcelDataReader; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Task…

Qt_day4_Qt_UI设计

目录 Qt_UI设计 1. Designer 设计师&#xff08;掌握&#xff09; 2. Layout 布局&#xff08;重点&#xff09; 2.1 基本使用 2.2 高级用法 2.3 代码布局&#xff08;了解&#xff09; 3. Designer与C的关系&#xff08;熟悉&#xff09; 4. 基本组件&#xff08;掌握…

Axure网络短剧APP端原型图,竖屏微剧视频模版40页

作品概况 页面数量&#xff1a;共 40 页 使用软件&#xff1a;Axure RP 9 及以上&#xff0c;非软件无源码 适用领域&#xff1a;短剧、微短剧、竖屏视频 作品特色 本作品为网络短剧APP的Axure原型设计图&#xff0c;定位属于免费短剧软件&#xff0c;类似红果短剧、河马剧场…

网安加·百家讲坛 | 仝辉:金融机构鸿蒙应用安全合规建设方案

作者简介&#xff1a;仝辉&#xff0c;北京娜迦信息科技发展有限公司攻防安全负责人&#xff0c;深耕移动应用安全领域十余年&#xff0c;获得过CISP、CISSP、OSCP、PMP、CCRC-CIASW等相关证书&#xff0c;参与多项移动应用安全标准起草&#xff0c;参与华为、平安集团、中国移…