建议参考RustChinaConf2020-23.邓建平-《用过程宏简化Rust代码》 https://github.com/rustaceanclub/RustChinaConf2020
file:///Users/fliter/rust-in-databend/32-%E7%AC%AC%E5%85%AD%E8%AF%BE-%E7%90%86%E8%A7%A3Rust%E5%AE%8F/%E7%AC%AC%E5%85%AD%E8%AF%BE%20%E7%90%86%E8%A7%A3Rust%E5%AE%8F.pdf
proc_macro_attribute、proc_macro、proc_macro_derive 有何作用?举例说明
这三者都是Rust中宏(macro)重要的概念:
proc_macro_attribute: 定义属性自定义宏。可以对某个语法元素添加被 decorate 的功能。
1
2#[my_attribute]
struct MyStruct;proc_macro: 定义表现层(procedural)宏。可以执行自定义的转换转化目标代码。
1
2
3macro_rules! my_macro {
() => { /* code */ }
}proc_macro_derive: 定义派生(derive)属性宏。可以自动为结构体/枚举实现特定 trait。
1
2#[derive(MyTrait)]
struct MyStruct;
使用例子:
1 |
|
通过这三种方式,开发者可以自定义注解、表现层宏和属性式派生,大大扩展了Rust语言本身的功能。
更多可参考 Rust 中的宏
宏(英语:Macro)是一种批量处理的称谓。
计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。
宏这一术语也常常被用于许多类似的环境中,它们是源自宏展开的概念,这包括键盘宏和宏语言。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。
宏的用途在于自动化频繁使用的序列 或者是获得一种更强大的抽象能力。
demo1
1 | macro_rules! my_macro { |
输出:
1 | Compiling macroo v0.1.0 (/Users/fliter/rust-lab/macroo) |
如果上面这段main()放在macro_rules! my_macro之前,也会报错:
demo2
1 | macro_rules! my_macro { |
输出:
1 | Compiling macroo v0.1.0 (/Users/fliter/rust-lab/macroo) |
demo3–实现特质trait
源码中很多这样的写法~
1 | trait AsBytes { |
输出:
1 | Compiling macroo v0.1.0 (/Users/fliter/rust-lab/macroo) |
demo4–控制权限
rust中 assert_eq!是宏吗? 请帮我列出rust中全部宏及其作用
是的,assert_eq! 是一个宏(macro)。
在 Rust 中,宏是一种在编译时进行代码转换和代码生成的特殊语法结构。宏可以接受输入参数,并在编译时根据这些参数进行代码展开和替换。宏的使用通过感叹号 ! 进行标识。
以下是一些常见的 Rust 内置宏及其作用:
println!和eprintln!:用于在标准输出和标准错误输出打印文本。format!:用于创建格式化的字符串。assert!和assert_eq!:用于在运行时进行断言检查,确保条件为真或值相等。panic!:用于在程序中触发一个 panic,即非恢复性的错误。vec!:用于创建包含初始值的向量。vec_macro!:用于创建包含可变数量元素的向量。if!和if_else!:用于在编译时进行条件判断,生成不同的代码块。match!:用于在编译时进行模式匹配,生成相应的代码块。cfg!:用于在编译时进行条件编译,根据给定的条件生成不同的代码。unsafe:用于标记包含不安全代码的块,允许绕过 Rust 的安全性检查。
以上只是列举了一些常见的 Rust 内置宏,实际上还有更多的宏可供使用。此外,Rust 还支持自定义宏,允许开发者创建自己的宏来简化代码和实现特定的功能。
注意,宏的具体行为和功能在不同的 Rust 版本中可能会有所变化,因此建议查阅 Rust 官方文档以获取准确和最新的信息。
能否给我一个自定义宏的例子?
当然!以下是一个简单的示例,展示如何创建一个自定义宏:
1 | macro_rules! greet { |
在这个例子中,我们定义了一个名为 greet 的宏。宏使用 macro_rules! 关键字来定义,并遵循一种类似于模式匹配的语法。在宏定义中,我们使用了两个模式:
()模式匹配空括号,表示没有参数传入宏时的情况。($name:expr)模式匹配一个表达式,并将其绑定到$name变量中。
根据不同的模式匹配,宏会展开成不同的代码块。在展开过程中,我们可以在代码中使用 $name 变量,它会被实际传入的参数替换。
在 main 函数中,我们分别调用了 greet! 宏,并传入不同的参数。根据传入的参数,宏会展开成相应的输出语句。
自定义宏可以用于简化重复的代码片段、实现代码生成、进行元编程等。它们在 Rust 中被广泛使用,尤其是在一些框架和库中,以提供更高级的抽象和功能。
https://time.geekbang.org/column/article/481355
宏的主要作用是避免我们创建大量结构相同的脚手架代码
宏编程主要的工作就是把一棵语法树转换成另一颗语法树,而这个转换的过程深入下去,不过就是数据结构到数据结构的转换
所以在开发的时候,要非常谨慎地构建宏。多问自己:我非用宏不可么?可以使用别的设计来避免使用宏么?同样是 Web 框架,rocket 使用宏做路由,axum 完全不使用宏。
就像 unsafe 一样,我们要把宏编程作为撰写代码最后的手段。当一个功能可以用函数表达时,不要用宏。不要过分迷信于编译时的处理,不要把它当成提高性能的手段。如果你发现某个设计似乎不得不使用宏,你需要质疑一下,自己设计上的选择是否正确。
file:///Users/fliter/rust-in-databend/03-%E9%80%9A%E8%BF%87%E5%AE%9E%E6%88%98%E7%90%86%E8%A7%A3Rust%E5%AE%8F-%E8%8B%8F%E6%9E%97/three-%E9%80%9A%E8%BF%87%E5%AE%9E%E6%88%98%E7%90%86%E8%A7%A3Rust%E5%AE%8F.pdf
2024.07.02 补充
我们已经使用了诸如println!这样的宏,但还没有深入探讨宏是什么以及它是如何工作的。宏在Rust中指的是一组特性,包括声明式宏(使用macro_rules!)和三种过程宏。我们将依次讨论这些宏,但首先让我们看看为什么在已经有了函数的情况下我们还需要宏。
从根本上说,宏是一种编写代码以生成其他代码的方式,这被称为元编程。我们经常使用println!和vec!宏,这些宏都会展开成更多的代码,你将得到比你实际编写的更多代码。元编程对于减少你需要编写和维护的代码量非常有用,这也是函数的作用之一。
然而,宏具有一些函数所没有的额外功能:
函数签名必须声明函数的参数数量和类型,而宏可以接受可变数量的参数。我们可以用一个参数调用宏,也可以用两个参数调用。
宏在编译器编译代码之前展开,因此宏可以在给定类型上实现trait,而函数则不行,因为函数是在运行时调用的,而trait要求在编译前被实现。
宏的缺点是宏定义比函数定义更复杂,因为你在编写生成Rust代码的Rust代码。由于这种间接性,宏定义通常比函数定义更难阅读、理解和维护。
宏和函数之间的另一个重要区别是,你必须在调用宏之前定义它们或将它们引入作用域,而函数可以在任何地方定义并在任何地方调用。举个简单的例子,如果把一个宏定义在main函数下面,然后在main函数中调用这个宏,编译器会报错并提示找不到宏。
在Rust中最广泛使用的宏形式是声明式宏。这些宏有时也被称为”macro_rules!宏”、”规则宏”或简单地称为”宏”。声明式宏允许你编写类似于Rust match表达式的东西,将一个值与特定代码相关的模式进行比较。在这种情况下,值是传递给宏的字面Rust源代码,模式与该源代码的结构进行比较。与每个模式相关的代码在匹配时替换传递给宏的代码。这一切都发生在编译期间。
要定义声明式宏,可以使用macro_rules!。让我们通过查看vec!宏的定义来探索如何使用macro_rules!。我们介绍了如何使用vec!宏创建包含特定值的新向量。例如,以下宏调用创建了一个包含三个整数的新向量:
1 | let v: Vec<i32> = vec![1, 2, 3]; |
我们还可以使用vec!宏来创建包含两个整数或五个字符串切片的向量。我们无法使用函数来实现相同的功能,因为我们无法预先知道值的数量或类型。
下面是vec!宏的简化定义:
1 |
|
让我们逐步解释这个宏定义:
#[macro_export]注解表示当宏定义所在的crate被引入作用域时,这个宏应该可用。没有这个注解,宏无法被引入作用域。我们用
macro_rules!和宏的名称(不带感叹号)开始宏定义。在这个例子中,名称是vec。宏定义的主体结构类似于match表达式。这里我们有一个由括号包裹的模式,后面是指向与该模式相关的代码块的
=>。在模式中,
$用于在宏系统中声明一个变量。$x:expr匹配任何Rust表达式,并将该表达式命名为$x。紧随
$(...)之后的逗号表示在与$(...)中代码匹配的代码之后可以出现零个或一个逗号分隔符。*指定该模式匹配零个或多个在之前的任何内容。在代码块中,我们创建一个新的向量,然后对每个匹配的表达式(
$x)重复temp_vec.push()。最后,我们返回生成的向量。
当我们调用vec![1, 2, 3]时,宏展开后生成的代码如下:
1 | { |
这样,我们定义了一个宏,它可以接受任意数量的任意类型的参数,并可以生成创建包含指定元素的向量的代码。
接下来,让我们讨论第二种形式的宏:过程宏。过程宏更像是一个函数,它接受一些代码作为输入,对该代码进行操作,并生成一些代码作为输出,而不是像声明式宏那样匹配模式并用其他代码替换代码。
过程宏有三种类型:
- 自定义派生宏
- 属性宏
- 函数式宏
这些宏的工作方式类似。过程宏的定义函数接受一个TokenStream作为输入,并生成一个TokenStream作为输出。TokenStream类型由Rust自带的proc_macro crate定义,表示一系列的标记。这是宏操作的源代码构成的输入TokenStream,而宏生成的代码则是输出TokenStream。
让我们创建一个名为hello_macro的crate,该crate定义了一个名为HelloMacro的trait,该trait包含一个名为hello_macro的关联函数。为了避免让用户为每个类型实现HelloMacro trait,我们将提供一个过程宏,使用户可以通过在它们的类型上注解#[derive(HelloMacro)]来获得hello_macro函数的默认实现。默认实现将打印一句话,其中包含使用该宏的类型的名称。
首先,我们创建一个新的crate:
1 | // src/lib.rs |
接下来,我们定义过程宏。在hello_macro项目中创建一个名为hello_macro_derive的crate:
1 | [lib] |
然后,在hello_macro_derive的lib.rs文件中添加以下代码:
1 | extern crate proc_macro; |
这个过程宏做以下几件事:
- 使用syn将输入的TokenStream解析为语法树。
- 使用quote!宏生成实现HelloMacro trait的代码。
- 将生成的代码转换回TokenStream。
现在,当用户在他们的类型上使用#[derive(HelloMacro)]时,这个过程宏将自动实现HelloMacro trait。
属性宏与自定义派生宏类似,但它们允许你创建新的属性。它们也更加灵活,可以应用于函数等其他项目。例如:
1 |
|
属性宏的定义函数签名如下:
1 |
|
这里有两个TokenStream参数:第一个用于属性的内容(例如GET, “/“),第二个用于属性所附加项的主体(在这种情况下是fn index()及函数的其余部分)。
最后,函数式宏定义看起来像函数调用的宏。例如:
1 | let sql = sql!(SELECT * FROM posts WHERE id=1); |
函数式宏的定义如下:
1 |
|
这种定义类似于自定义派生宏的签名。我们接收括号内的标记,并返回我们想要生成的代码。
总的来说,Rust的宏系统提供了强大的元编程能力,允许我们编写更简洁、更灵活的代码。虽然宏可能比函数更复杂,但它们在某些情况下可以大大提高代码的表达能力和可维护性。
原文链接: https://dashen.tech/2020/09/30/Rust中的宏/
版权声明: 转载请注明出处.