Rust 宏笔记

本贴最后更新于 1835 天前,其中的信息可能已经沧海桑田


这篇文章说的是?

Rust 的宏。


宏按照来源分类:

声明宏(Declarative Macro)和过程宏(Procedural Macro)。前者指的是用某种语法直接声明出的宏。后者是对应直接生成抽象语法树的过程的宏。

直觉上过程宏更隐式,更全能;声明宏更可读,更直接。


如何定义声明宏?

现在用 macro_rules!。以后可能还有别的办法。


如何定义过程宏?

以后再说。


宏按照使用方式分类:

属性宏:给声明添加属性的宏,例如 #[derive(Debug)]#[test]

调用宏:像函数一样的宏,例如 println!


来源分类和使用方式分类之间的关系如何?

目前的声明宏都是用 macro_rules! 声明出的,它声明出的一定是调用宏。过程宏可以产生属性宏,也可以产生调用宏。

也就是说,属性宏都是过程宏,调用宏可能是声明宏或者过程宏。


println! 宏大概是什么样子?

macro_rules! println { () => (println!("\n")); ($fmt: expr) => (print!(concat!($fmt, "\n"))); ($fmt: expr, $($(arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); }

这个宏有几部分?

有三个部分,输入分别是 ()($fmt:expr)($fmt: expr, $($args:tt)*),依次扩展成 => 后,圆括号内部的部分。每个部分是一条规则,每条规则以 ; 结尾。


=> 后的圆括号是必须的吗?

不能省略,但是可以换成 {} 或者 []


$fmt: expr 是什么?

$fmt 是对宏参数的捕获,类似于函数的参数。expr 表示这个捕获的类型是表达式,也就是会它会生成具体的值。具体到此处,它代表生成 println! 的格式字符串的表达式。


捕获有什么用?

宏的替换结果里可以用 $fmt 代表要替换这个捕获。例如 println!("Hello") 中,$fmt: expr 捕获了 "Hello",所以 print!(concat!($fmt, "\n)) 中的 $fmt 会被替换为 "Hello",所以展开成 print!(concat!("Hello", "\n"))


展开的宏中如果还有宏,还会继续展开吗?

会,上面 println! 展开之后的内容中有 print!concat!,它们都会再次展开。这是理所当然的行为,这个问题只是为了让 Rust 的宏跟 C++ 的宏划清界限。


都有什么捕获类型?

类型 意义
item 语言项,模块、定义、声明等
block 代码块,花括号限定的代码
stmt 语句,分号结尾的代码
expr 表达式,会生成具体的值
pat 模式
ty 类型
ident 标识符
path 路径,指从 crate 到 mod 的定位
meta 元信息
tt TokenTree 的缩写,词条树
vis 可见性,例如 pub
lifetime 生命周期参数

$($arg:tt)* 是什么意思?

单独看 $arg:tt 表示匹配一个词条树的捕获,在外面套上 $()* 表示匹配若干次词条树。


$($arg)* 是什么意思?

单看 $arg,表示在宏里替换捕获 $arg,外面套上 $()* 表示使用所有匹配的捕获。这个用法跟它的捕获语法是对应的。


$($arg:tt)* 能匹配什么?

println! 在第一个参数之后的所有东西。这个宏不止可以传递像函数一样的参数,还可以像 Python 那样传递命名参数,例如:

println!("Hello, {name}", name="Luna");

这样的参数 $($arg:tt)* 也能捕获到。


如果想只捕获(不定个数个)函数参数应该如何做?

$($arg: expr),* 或者 $($arg: expr,)*


这两个有什么不同?

后者也匹配逗号结尾的参数列表。

Rust 的函数参数列表最后可以添加逗号,也可以不加。如果想让宏表现的尽量接近函数,应该两种情况都处理。


举个例子。

macro_rules! hash_map { ($($key:expr => $value:expr),*) => {{ let mut map = ::std::collections::HashMap::new(); $( map.insert($key, $value); )* map }}; ($($key:expr => $value:expr),*) => (hash_map! ($($key => $value),*)); }

怎么使用?

let map = hash_map! (1 => "one", 2 => "two", 3 => "three");

如何获取宏可变参数的长度?

没有直接的办法,但可以想些技巧。

macro_rules! unit { ($($x:tt)*) => (()); } macro_rules! count { ($($x:expr),*) => (<[()]>::len(&[$(unit!($x)),*])); }

它是如何工作的?

unit! 接受任意什么东西,返回一个 unit(())。count! 宏把参数填给 unit!,构造了一个 unit 数组,数组长度就是参数的个数。

好处是,unit 不占空间,unit 的数组也是。


count! 宏需要 unit! 宏才能工作,但 unit! 宏本身没什么用,能不能把 unit! 变成私有的?

也没有直接的办法。基本上的技巧是换成一个比较难直接用到的规则:

macro_rules! count { (@unit $($x:tt)*) => (()); ($($x:expr),*) => (<[()]>::len(&[$(count!(@unit $x)),*])); }

要是有人非要写 count!(@unit ...),也没法阻止。但那个奇奇怪怪的 @ 已经暗示了这是一个内部实现,这就足够了。


#[macro_export] 有什么作用?

标记一个宏可以在其他包中使用。也就是说,默认情况下,宏不能在定义的包外使用。


#[macro_use] 有什么用?

在 Rust 2015 中,在外部包的声明语前,用它标记要导入另一个包的宏。另外,它还标记一个 mod 的宏可以在外面使用。


宏重名会怎样?

后导入的会覆盖先前导入的,不会发生错误。


$crate 有什么用?

它看起来像一个捕获,但不是在宏参数列表里捕获的。它会扩展成当前包的名字。需要这个捕获的原因是,当前包无法决定此包被其他包导入的时候,使用的是什么名字。所以如果要使用当前包的函数或者别的东西,就需要从 $crate 开始写路径。


完。

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    59 引用 • 22 回帖 • 12 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 350 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 537 关注
  • OneDrive
    2 引用 • 4 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1282 回帖 • 1 关注
  • AWS
    11 引用 • 28 回帖 • 8 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    268 引用 • 666 回帖 • 1 关注
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    291 引用 • 4495 回帖 • 661 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 52 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    63 引用 • 289 回帖
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 2 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    10 引用 • 15 回帖 • 5 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    36 引用 • 200 回帖 • 33 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    22 引用 • 148 回帖 • 7 关注
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    4 引用 • 7 回帖 • 4 关注
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 196 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 662 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 59 回帖 • 3 关注
  • uTools

    uTools 是一个极简、插件化、跨平台的现代桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

    7 引用 • 28 回帖 • 1 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖 • 1 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 534 回帖
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 182 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    44 引用 • 208 回帖 • 1 关注
  • OnlyOffice
    4 引用 • 15 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖 • 1 关注