├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .gitmodules
├── LICENSE-MIT
├── README.md
├── book.toml
├── src
├── SUMMARY.md
├── decl-macros.md
├── decl-macros
│ ├── building-blocks.md
│ ├── building-blocks
│ │ ├── abacus-counting.md
│ │ ├── ast-coercion.md
│ │ ├── counting.md
│ │ └── parsing.md
│ ├── macros-methodical.md
│ ├── macros-practical-table.html
│ ├── macros-practical.md
│ ├── macros2.md
│ ├── minutiae.md
│ ├── minutiae
│ │ ├── debugging.md
│ │ ├── fragment-specifiers.md
│ │ ├── hygiene.md
│ │ ├── identifiers.md
│ │ ├── import-export.md
│ │ ├── metavar-and-expansion.md
│ │ ├── metavar-expr.md
│ │ └── scoping.md
│ ├── patterns.md
│ └── patterns
│ │ ├── callbacks.md
│ │ ├── internal-rules.md
│ │ ├── push-down-acc.md
│ │ ├── repetition-replacement.md
│ │ ├── tt-bundling.md
│ │ └── tt-muncher.md
├── introduction.md
├── proc-macros.md
├── proc-macros
│ ├── hygiene.md
│ ├── methodical.md
│ ├── methodical
│ │ ├── attr.md
│ │ ├── derive.md
│ │ └── function-like.md
│ ├── practical.md
│ └── third-party-crates.md
├── syntax-extensions.md
├── syntax-extensions
│ ├── ast.md
│ ├── debugging.md
│ ├── expansion.md
│ ├── hygiene.md
│ └── source-analysis.md
└── translation_statement.md
└── theme
├── css
├── chrome.css
├── general.css
└── variables.css
├── index.hbs
├── pagetoc.css
├── pagetoc.js
└── rust-syntax-bg-highlight.css
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Mdbook build
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 |
7 | jobs:
8 | build:
9 | name: build
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write # To push a branch
13 | pull-requests: write # To create a PR from that branch
14 | env:
15 | MDBOOK_VERSION: '0.4.40'
16 | # MDBOOK_MERMAID_VERSION: '0.13.0'
17 | steps:
18 | - uses: actions/checkout@v4
19 | with:
20 | submodules: false
21 | - name: Install mdbook
22 | env:
23 | GH_TOKEN: ${{ github.token }}
24 | run: |
25 | mkdir ~/tools
26 | curl -L https://github.com/rust-lang/mdBook/releases/download/v$MDBOOK_VERSION/mdbook-v$MDBOOK_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/tools
27 | # curl -L https://github.com/badboy/mdbook-mermaid/releases/download/v$MDBOOK_MERMAID_VERSION/mdbook-mermaid-v$MDBOOK_MERMAID_VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xz -C ~/tools
28 |
29 | gh release download -R Michael-F-Bryan/mdbook-linkcheck -p mdbook-linkcheck.x86_64-unknown-linux-gnu.zip
30 | unzip mdbook-linkcheck.x86_64-unknown-linux-gnu.zip -d ~/tools
31 | chmod +x ~/tools/mdbook-linkcheck
32 |
33 | gh release download -R zjp-CN/mdbook-theme -p mdbook-theme_linux.tar.gz
34 | tar -xzf mdbook-theme_linux.tar.gz -C ~/tools
35 | echo ~/tools >> $GITHUB_PATH
36 | - name: Build
37 | run: mdbook build
38 |
39 | - name: Upload pages artifacts
40 | uses: actions/upload-pages-artifact@v3
41 | with:
42 | path: book
43 |
44 | # Deploy job
45 | deploy:
46 | # Add a dependency to the build job
47 | needs: build
48 |
49 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
50 | permissions:
51 | pages: write # to deploy to Pages
52 | id-token: write # to verify the deployment originates from an appropriate source
53 |
54 | # Deploy to the github-pages environment
55 | environment:
56 | name: github-pages
57 | url: ${{ steps.deployment.outputs.page_url }}
58 |
59 | # Specify runner + deployment step
60 | runs-on: ubuntu-latest
61 | steps:
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /book
2 | tmp.sh
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "tlborm-chinese"]
2 | path = tlborm-chinese
3 | url = https://github.com/DaseinPhaos/tlborm-chinese.git
4 | [submodule "tlborm"]
5 | path = tlborm
6 | url = https://github.com/Veykril/tlborm.git
7 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
2 |
3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rust 宏小册
2 |
3 | > 注意:这是对 [Daniel Keep 撰写的书](https://github.com/DanielKeep/tlborm)
4 | > 的续写,自 2016 年初夏以来,那本书就没再更新。
5 |
6 | 本书的续写者为 [Veykril](https://github.com/veykril),使用
7 | [mdBook](https://github.com/rust-lang/mdBook) 工具生成。你可以浏览本书的
8 | [英文版本](https://veykril.github.io/tlborm/),和 github
9 | [仓库](https://github.com/veykril/tlborm)。[^translation-statement]
10 |
11 | 这本书尝试提炼出 Rust 社区对 Rust 宏的共识,准确地说,是 *通过例子* 来讲述宏[^macros]。
12 | 因此,欢迎 PR 补充和提 issue。
13 |
14 | 如果你对某些书中的内容不清楚,或者不理解,别害怕提 issue
15 | 来请求澄清那部分。本书的目标是尽可能成为最好的(宏)学习资料。
16 |
17 | 在我学习 Rust 的时候,*Little Book of Rust Macros* [原作](https://github.com/DanielKeep/tlborm)
18 | *通过例子* 的方式非常给力地帮助过我理解(声明)宏。很遗憾的是,Rust
19 | 语言与宏系统持续改进时,原作者不再更新书籍。
20 |
21 | 这也是我想尽可能地更新这本书的原因,并且我尽可能地把新发现的事情增加到书中,以帮助新的
22 | Rust 宏学习者理解宏系统 —— 这个让很多人困惑的部分。
23 |
24 | > 这本书认为你应该对 Rust 有基本的了解,它不会解释 Rust
25 | > 语言特性或者与宏无关的结构,但不会假设你提前掌握宏的知识。
26 | >
27 | > 你必须至少阅读和理解了 [Rust Book](https://doc.rust-lang.org/stable/book/)
28 | > 的前七章 —— 当然,建议你阅读完 Rust Book 的大部分内容。
29 |
30 | [^translation-statement]:译者注:我对原作和续作进行了梳理,见 [翻译说明](./translation_statement.html)。
31 |
32 | [^macros]: 译者注:2022 年的中文版随续作更新了过程宏,而声明宏也一直在演进。
33 |
34 | ## 致谢
35 |
36 | 非常感谢 [Daniel Keep](https://github.com/DanielKeep/tlborm) 最初写下这本书。[^thanks]
37 |
38 | 感谢对原书提出建议和更正的读者:
39 | IcyFoxy、 Rym、 TheMicroWorm、 Yurume、 akavel、 cmr、 eddyb、 ogham 和 snake_case。
40 |
41 | [^thanks]: 译者注:非常感谢 Veykril 不懈地更新此书。感谢
42 | [DaseinPhaos](https://github.com/DaseinPhaos/tlborm-chinese) 对原作的翻译。此外,本书的右侧
43 | TOC 是由 [mdbook-theme](https://github.com/zjp-CN/mdbook-theme) 所提供。
44 |
45 | ## 版权声明
46 |
47 | 这本书沿袭了原作的版权声明,因此具有 [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/) 和 [MIT license](http://opensource.org/licenses/MIT) 的双重许可。
48 |
--------------------------------------------------------------------------------
/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Daniel Keep", "Veykril", "DaseinPhaos", "zjp"]
3 | language = "zh"
4 | multilingual = true
5 | src = "src"
6 | title = "The Little Book of Rust Macros (Rust 宏小册)"
7 |
8 | # [rust]
9 | # edition = "2021"
10 |
11 | # [preprocessor.theme]
12 | # pagetoc = true
13 | # pagetoc-width = "12%"
14 | # content-max-width = "78%"
15 | # sidebar-width = "255px"
16 | # sidebar-font-size = "0.85em"
17 | # content-main-margin-left = "4%"
18 | # content-main-margin-right = "4%"
19 | # nav-chapters-max-width = "3%"
20 | # nav-chapters-min-width = "3%"
21 |
22 | [build]
23 | create-missing = true
24 | build-dir = "book"
25 |
26 | [output.html]
27 | default-theme = "ayu"
28 | site-url = "/tlborm/"
29 | mathjax-support = true
30 | git-repository-url = "https://github.com/zjp-CN/tlborm"
31 | additional-css = ["theme/pagetoc.css", "theme/rust-syntax-bg-highlight.css"]
32 | additional-js = ["theme/pagetoc.js"]
33 |
34 | [output.html.fold]
35 | enable = false
36 | level = 1
37 |
38 | [output.html.playground]
39 | editable = true
40 |
41 | [output.html.print]
42 | enable = false
43 |
44 | [output.theme-ace]
45 | theme-white = "xcode"
46 | theme-dark = "monokai"
47 |
--------------------------------------------------------------------------------
/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | [简介](./introduction.md)
4 |
5 | - [语法拓展](./syntax-extensions.md)
6 | - [源代码分析](./syntax-extensions/source-analysis.md)
7 | - [AST 中的宏](./syntax-extensions/ast.md)
8 | - [宏展开](./syntax-extensions/expansion.md)
9 | - [卫生性](./syntax-extensions/hygiene.md)
10 | - [调试](./syntax-extensions/debugging.md)
11 | - [声明宏](./decl-macros.md)
12 | - [思路](./decl-macros/macros-methodical.md)
13 | - [实战](./decl-macros/macros-practical.md)
14 | - [细节](./decl-macros/minutiae.md)
15 | - [片段分类符](./decl-macros/minutiae/fragment-specifiers.md)
16 | - [再谈元变量与宏展开](./decl-macros/minutiae/metavar-and-expansion.md)
17 | - [元变量表达式](./decl-macros/minutiae/metavar-expr.md)
18 | - [宏是部分“卫生的”](./decl-macros/minutiae/hygiene.md)
19 | - [非标识符的“标识符”](./decl-macros/minutiae/identifiers.md)
20 | - [调试](./decl-macros/minutiae/debugging.md)
21 | - [作用域](./decl-macros/minutiae/scoping.md)
22 | - [导入/导出宏](./decl-macros/minutiae/import-export.md)
23 | - [macro 2.0](./decl-macros/macros2.md)
24 | - [模式](./decl-macros/patterns.md)
25 | - [回调](./decl-macros/patterns/callbacks.md)
26 | - [`tt` “撕咬机“](./decl-macros/patterns/tt-muncher.md)
27 | - [内用规则](./decl-macros/patterns/internal-rules.md)
28 | - [下推累积](./decl-macros/patterns/push-down-acc.md)
29 | - [反复替换](./decl-macros/patterns/repetition-replacement.md)
30 | - [`tt` 捆绑](./decl-macros/patterns/tt-bundling.md)
31 | - [构件](./decl-macros/building-blocks.md)
32 | - [AST 强制转换](./decl-macros/building-blocks/ast-coercion.md)
33 | - [计数](./decl-macros/building-blocks/counting.md)
34 | - [算盘计数](./decl-macros/building-blocks/abacus-counting.md)
35 | - [解析](./decl-macros/building-blocks/parsing.md)
36 | - [过程宏](./proc-macros.md)
37 | - [思路](./proc-macros/methodical.md)
38 | - [函数式](./proc-macros/methodical/function-like.md)
39 | - [属性式](./proc-macros/methodical/attr.md)
40 | - [derive 式](./proc-macros/methodical/derive.md)
41 | - [实战]()
42 | - [第三方 crates](./proc-macros/third-party-crates.md)
43 | - [卫生性和 Span](./proc-macros/hygiene.md)
44 | - [技巧]()
45 |
46 | [翻译说明](./translation_statement.md)
47 |
--------------------------------------------------------------------------------
/src/decl-macros.md:
--------------------------------------------------------------------------------
1 | # 声明宏
2 |
3 | 本章将介绍 Rust 的声明宏系统:[`macro_rules!`](https://doc.rust-lang.org/reference/macros-by-example.html)。
4 |
5 | 在这一章中有两种不同的介绍,一个 [讲思路](./decl-macros/macros-methodical.md),另一个
6 | [讲实践](./decl-macros/macros-practical.md)。
7 |
8 | 前者会向你阐述一个完整而详尽的系统如何工作,而后者将涵盖更多的实际例子。
9 |
10 | 因此,[思路介绍](./decl-macros/macros-methodical.md)
11 | 是为那些只希望声明宏系统作为一个整体得到解释的人而设计的,而
12 | [实践介绍](./decl-macros/macros-practical.md) 则指导人们通过实现单个宏。
13 |
14 | 在这两个介绍之后,本章还提供了一些常规且有用的 [模式](./decl-macros/patterns.md)
15 | 和 [构件](./decl-macros/building-blocks.md),用于创建功能丰富的声明宏。
16 |
17 | 关于声明宏的其他资源:
18 | 1. Rust Book 的[宏章节](https://doc.rust-lang.org/book/ch19-06-macros.html),这是一个更平易近人的高级解释
19 | 2. Reference [macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)
20 | 章节,它更深入而精确地讨论了细节
21 |
22 | > 注意:本书在讨论声明宏时,通常会使用术语 *mbe* (**M**acro-**B**y-**E**xample)、 *mbe macro* 或
23 | > `macro_rules!`。
24 |
--------------------------------------------------------------------------------
/src/decl-macros/building-blocks.md:
--------------------------------------------------------------------------------
1 | # 构件
2 |
3 | 可重用的宏代码片段 (reusable snippets) 。(也可称作“轮子”)
4 |
--------------------------------------------------------------------------------
/src/decl-macros/building-blocks/abacus-counting.md:
--------------------------------------------------------------------------------
1 | # 算盘计数
2 |
3 | ## 描述分析
4 |
5 | > 临时信息:需要更合适的例子。
6 | 该用例采用 Rust 分组机制无法表示的匹配嵌套结构,
7 | 实在是过于特殊,因此不适作为例子使用。
8 |
9 | > 注意:此节假设读者已经了解 [下推累积](../patterns/push-down-acc.md)
10 | 以及 [标记树撕咬机](../patterns/tt-muncher.md) 。
11 |
12 | ```rust,editable
13 | macro_rules! abacus {
14 | ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
15 | abacus!(($($moves)*) -> ($($count)*))
16 | };
17 | ((- $($moves:tt)*) -> ($($count:tt)*)) => {
18 | abacus!(($($moves)*) -> (- $($count)*))
19 | };
20 | ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
21 | abacus!(($($moves)*) -> ($($count)*))
22 | };
23 | ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
24 | abacus!(($($moves)*) -> (+ $($count)*))
25 | };
26 |
27 | // Check if the final result is zero.
28 | (() -> ()) => { true };
29 | (() -> ($($count:tt)+)) => { false };
30 | }
31 |
32 | fn main() {
33 | let equals_zero = abacus!((++-+-+++--++---++----+) -> ());
34 | assert_eq!(equals_zero, true);
35 | }
36 | ```
37 |
38 | 这个例子所用的技巧用在如下情况:
39 | 记录的计数会发生变化,且初始值为零或在零附近,且必须支持如下操作:
40 |
41 | * 增加一;
42 | * 减少一;
43 | * 与 0 (或任何其它固定的有限值)相比较;
44 |
45 | 数值 n 将由一组共 n 个相同的特定标记来表示。
46 | 对数值的修改操作将采用 [下推累积](../patterns/push-down-acc.md) 模式由递归调用完成。
47 | 假设所采用的特定标记是 `x` ,则上述操作可实现为:
48 |
49 | * 增加一:匹配`($($count:tt)*)`并替换为`(x $($count)*)`。
50 | * 减少一:匹配`(x $($count:tt)*)`并替换为`($($count)*)`。
51 | * 与0相比较:匹配`()`。
52 | * 与1相比较:匹配`(x)`。
53 | * 与2相比较:匹配`(x x)`。
54 | * *(依此类推...)*
55 |
56 | 作用于计数值的操作将所选的标记来回摆动,如同算盘摆动算子。[^abacus]
57 |
58 |
59 | [^abacus]: 在这句极度单薄的辩解下,隐藏着选用此名称的 *真实* 理由:
60 | 避免造出又一个名含“标记”的术语。今天就该跟你认识的作者谈谈避免
61 | [语义饱和](https://en.wikipedia.org/wiki/Semantic_satiation) 吧!
62 | 公平来讲,本来也可以称它为
63 | [“一元计数(unary counting)”](https://en.wikipedia.org/wiki/Unary_numeral_system) 。
64 |
65 | 在想表示负数的情况下,值 *-n* 可被表示成 *n* 个相同的其它标记。
66 | 在上例中,值 *+n* 被表示成 *n* 个 `+` 标记,而值 *-m* 被表示成 *m* 个 `-` 标记。
67 |
68 | 有负数的情况下操作起来稍微复杂一些,
69 | 增减操作在当前数值为负时实际上互换了角色。
70 | 给定 `+` 和 `-` 分别作为正数与负数标记,相应操作的实现将变成:
71 |
72 | * 增加一:
73 | * 匹配 `()` 并替换为 `(+)`
74 | * 匹配 `(- $($count:tt)*)` 并替换为 `($($count)*)`
75 | * 匹配 `($($count:tt)+)` 并替换为 `(+ $($count)+)`
76 | * 减少一:
77 | * 匹配 `()` 并替换为 `(-)`
78 | * 匹配 `(+ $($count:tt)*)` 并替换为 `($($count)*)`
79 | * 匹配 `($($count:tt)+)` 并替换为 `(- $($count)+)`
80 | * 与 0 相比较:匹配 `()`
81 | * 与 +1 相比较:匹配 `(+)`
82 | * 与 -1 相比较:匹配 `(-)`
83 | * 与 +2 相比较:匹配 `(++)`
84 | * 与 -2 相比较:匹配 `(--)`
85 | * *(依此类推...)*
86 |
87 | 注意在顶部的示例中,某些规则被合并到一起了
88 | (举例来说,对 `()` 及 `($($count:tt)+)` 的增加操作被合并为对
89 | `($($count:tt)*)` 的增加操作)。
90 |
91 | 如果想要提取出所计数目的实际值,可再使用普通的
92 | [计数宏](../building-blocks/counting.md) 。对上例来说,终结规则可换为:
93 |
94 | ```rust,ignore
95 | macro_rules! abacus {
96 | // ...
97 |
98 | // 下列规则将计数替换成实际值的表达式
99 | (() -> ()) => {0};
100 | (() -> (- $($count:tt)*)) => {
101 | - ( count_tts!($( $count_tts:tt )*) )
102 | };
103 | (() -> (+ $($count:tt)*)) => {
104 | count_tts!($( $count_tts:tt )*)
105 | };
106 | }
107 |
108 | // 计数一章任选一个宏
109 | macro_rules! count_tts {
110 | // ...
111 | }
112 | ```
113 |
114 | > 仅限此例:
115 | 严格来说,想要达到此例的效果,没必要做的这么复杂。
116 | 如果你不需要在宏中匹配所计的值,可直接采用重复来更加高效地实现:
117 | >
118 | > ```RUST,ignore
119 | > macro_rules! abacus {
120 | > (-) => {-1};
121 | > (+) => {1};
122 | > ($( $moves:tt )*) => {
123 | > 0 $(+ abacus!($moves))*
124 | > }
125 | > }
126 | > ```
127 |
128 |
129 | ## 算盘游戏
130 |
131 | > 译者注:这章原作者的表述实在过于啰嗦,但是这个例子的确很有意思。
132 | 基于这个例子框架,我给出如下浅显而完整的样例代码(可编辑运行):
133 | ```rust,editable
134 | macro_rules! abacus {
135 | ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
136 | {
137 | println!("{} [-]{} | [+]{}", "-+1", stringify!($($moves)*), stringify!($($count)*));
138 | abacus!(($($moves)*) -> ($($count)*))
139 | }
140 | };
141 | ((- $($moves:tt)*) -> ($($count:tt)*)) => {
142 | {
143 | println!("{} [-]{} | - {}", "- 2", stringify!($($moves)*), stringify!($($count)*));
144 | abacus!(($($moves)*) -> (- $($count)*))
145 | }
146 | };
147 | ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
148 | {
149 | println!("{} [+]{} | [-]{}", "+-3", stringify!($($moves)*), stringify!($($count)*));
150 | abacus!(($($moves)*) -> ($($count)*))
151 | }
152 | };
153 | ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
154 | {
155 | println!("{} [+]{} | + {}", "+ 4", stringify!($($moves)*), stringify!($($count)*));
156 | abacus!(($($moves)*) -> (+ $($count)*))
157 | }
158 | };
159 |
160 | (() -> ()) => {0};
161 | (() -> (- $($count:tt)*)) => {{-1 + abacus!(() -> ($($count)*)) }};
162 | (() -> (+ $($count:tt)*)) => {{1 + abacus!(() -> ($($count)*)) }};
163 | }
164 |
165 | fn main() {
166 | println!("算盘游戏:左边与右边异号时抵消;非异号时,把左边的符号转移到右边;左边无符号时,游戏结束,计算右边得分");
167 | println!("图示注解:左右符号消耗情况,分支编号,[消失的符号] 左边情况 | [消失的符号] 右边情况\n");
168 |
169 | println!("计数结果:{}\n", abacus!((++-+-+) -> (--+-+-)));
170 | println!("计数结果:{}\n", abacus!((++-+-+) -> (++-+-+)));
171 | println!("计数结果:{}\n", abacus!((---+) -> ()));
172 | println!("计数结果:{}\n", abacus!((++-+-+) -> ()));
173 | println!("计数结果:{}\n", abacus!((++-+-+++--++---++----+) -> ())); // 这是作者给的例子 :)
174 | }
175 | ```
176 |
177 | 打印结果:
178 |
179 | ```text
180 | 算盘游戏:左边与右边异号时抵消;非异号时,把左边的符号转移到右边;左边无符号时,游戏结束,计算右边得分
181 | 图示注解:左右符号消耗情况,分支编号,[消失的符号] 左边情况 | [消失的符号] 右边情况
182 |
183 | +-3 [+]+ - + - + | [-]- + - + -
184 | +-3 [+]- + - + | [-]+ - + -
185 | -+1 [-]+ - + | [+]- + -
186 | +-3 [+]- + | [-]+ -
187 | -+1 [-]+ | [+]-
188 | +-3 [+] | [-]
189 | 计数结果:0
190 |
191 | + 4 [+]+ - + - + | + + + - + - +
192 | + 4 [+]- + - + | + + + + - + - +
193 | -+1 [-]+ - + | [+]+ + + - + - +
194 | + 4 [+]- + | + + + + - + - +
195 | -+1 [-]+ | [+]+ + + - + - +
196 | + 4 [+] | + + + + - + - +
197 | 计数结果:4
198 |
199 | - 2 [-]- - + | -
200 | - 2 [-]- + | - -
201 | - 2 [-]+ | - - -
202 | +-3 [+] | [-]- -
203 | 计数结果:-2
204 |
205 | + 4 [+]+ - + - + | +
206 | + 4 [+]- + - + | + +
207 | -+1 [-]+ - + | [+]+
208 | + 4 [+]- + | + +
209 | -+1 [-]+ | [+]+
210 | + 4 [+] | + +
211 | 计数结果:2
212 |
213 | + 4 [+]+ - + - + + + - - + + - - - + + - - - - + | +
214 | + 4 [+]- + - + + + - - + + - - - + + - - - - + | + +
215 | -+1 [-]+ - + + + - - + + - - - + + - - - - + | [+]+
216 | + 4 [+]- + + + - - + + - - - + + - - - - + | + +
217 | -+1 [-]+ + + - - + + - - - + + - - - - + | [+]+
218 | + 4 [+]+ + - - + + - - - + + - - - - + | + +
219 | + 4 [+]+ - - + + - - - + + - - - - + | + + +
220 | + 4 [+]- - + + - - - + + - - - - + | + + + +
221 | -+1 [-]- + + - - - + + - - - - + | [+]+ + +
222 | -+1 [-]+ + - - - + + - - - - + | [+]+ +
223 | + 4 [+]+ - - - + + - - - - + | + + +
224 | + 4 [+]- - - + + - - - - + | + + + +
225 | -+1 [-]- - + + - - - - + | [+]+ + +
226 | -+1 [-]- + + - - - - + | [+]+ +
227 | -+1 [-]+ + - - - - + | [+]+
228 | + 4 [+]+ - - - - + | + +
229 | + 4 [+]- - - - + | + + +
230 | -+1 [-]- - - + | [+]+ +
231 | -+1 [-]- - + | [+]+
232 | -+1 [-]- + | [+]
233 | - 2 [-]+ | -
234 | +-3 [+] | [-]
235 | 计数结果:0
236 | ```
237 |
238 |
--------------------------------------------------------------------------------
/src/decl-macros/building-blocks/ast-coercion.md:
--------------------------------------------------------------------------------
1 | # AST 强制转换
2 |
3 | 在替换 `tt` 时,Rust 的解析器并不十分可靠。
4 | 当它期望得到某类特定的语法结构时,
5 | 如果摆在它面前的是一坨替换后的 `tt` 标记,就有可能出现问题。
6 | 解析器常常直接选择放弃解析,而非尝试去解析它们。
7 | 在这类情况中,就要用到 AST 强制转换(简称“强转”)。
8 |
9 | ```rust,editable
10 | #![allow(dead_code)]
11 |
12 | macro_rules! as_expr { ($e:expr) => {$e} }
13 | macro_rules! as_item { ($i:item) => {$i} }
14 | macro_rules! as_pat { ($p:pat) => {$p} }
15 | macro_rules! as_stmt { ($s:stmt) => {$s} }
16 | macro_rules! as_ty { ($t:ty) => {$t} }
17 |
18 | fn main() {
19 | as_item!{struct Dummy;}
20 |
21 | as_stmt!(let as_pat!(_): as_ty!(_) = as_expr!(42));
22 | }
23 | ```
24 |
25 | 这些强制变换经常与 [下推累积][push-down accumulation] 宏一同使用,
26 | 以使解析器能够将最终输出的 `tt` 序列当作某类特定的语法结构来对待。
27 |
28 | 注意:之所以只有这几种强转宏,
29 | 是由宏 **可以展开成什么** 所决定的,
30 | 而不是由宏能够捕捉哪些东西所决定的。
31 |
32 | [push-down accumulation]: ../patterns/push-down-acc.html
33 |
--------------------------------------------------------------------------------
/src/decl-macros/building-blocks/counting.md:
--------------------------------------------------------------------------------
1 | # 计数
2 |
3 | ## 反复替换
4 |
5 | 在宏中计数是一项让人吃惊的难搞的活儿。
6 | 最简单的方式是采用反复替换 (repetition with replacement) 。
7 |
8 | ```rust,editable
9 | macro_rules! replace_expr {
10 | ($_t:tt $sub:expr) => {$sub};
11 | }
12 |
13 | macro_rules! count_tts {
14 | ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
15 | }
16 | #
17 | # fn main() {
18 | # assert_eq!(count_tts!(0 1 2), 3);
19 | # }
20 | ```
21 |
22 | 对于小数目来说,这方法不错,但当输入量到达 500 [^outdated-repetition]左右的标记时,
23 | 很可能让编译器崩溃。想想吧,输出的结果将类似:
24 |
25 | ```rust,ignore
26 | 0usize + 1usize + /* ~500 `+ 1usize`s */ + 1usize
27 | ```
28 |
29 | 编译器必须把这一大串解析成一棵 AST ,
30 | 那可会是一棵完美失衡的 500 多级深的二叉树。
31 |
32 | [^outdated-repetition]:译者注:500 这个数据过时了,例子见下面 [递归](#递归) 第三个代码块。
33 |
34 | ## 递归
35 |
36 | 递归 (recursion) 是个老套路。
37 |
38 | ```rust,editable
39 | macro_rules! count_tts {
40 | () => {0usize};
41 | ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
42 | }
43 | #
44 | # fn main() {
45 | # assert_eq!(count_tts!(0 1 2), 3);
46 | # }
47 | ```
48 |
49 | > 注意:对于 `rustc` 1.2 来说,很不幸,
50 | 编译器在处理大数量的类型未知的整型字面值时将会出现性能问题。
51 | 我们此处显式采用 `usize` 类型就是为了避免这种不幸。
52 | >
53 | > 如果这样做并不合适(比如说,当类型必须可替换时),
54 | 可通过 `as` 来减轻问题。(比如, `0 as $ty`、`1 as $ty` 等)。
55 |
56 | 这方法管用,但很快就会超出宏递归的次数限制(
57 | [目前](https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute)
58 | 是 128 )。
59 |
60 | 与重复替换不同的是,可通过增加匹配分支来增加可处理的输入面值。
61 |
62 | 以下为增加匹配分支的改进代码[^recur-limit],如果把前三个分支注释掉,看看编译器会提示啥 :)
63 |
64 | ```rust,editable
65 | macro_rules! count_tts {
66 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
67 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
68 | $_k:tt $_l:tt $_m:tt $_n:tt $_o:tt
69 | $_p:tt $_q:tt $_r:tt $_s:tt $_t:tt
70 | $($tail:tt)*)
71 | => {20usize + count_tts!($($tail)*)};
72 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
73 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
74 | $($tail:tt)*)
75 | => {10usize + count_tts!($($tail)*)};
76 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
77 | $($tail:tt)*)
78 | => {5usize + count_tts!($($tail)*)};
79 | ($_a:tt
80 | $($tail:tt)*)
81 | => {1usize + count_tts!($($tail)*)};
82 | () => {0usize};
83 | }
84 |
85 | fn main() {
86 | assert_eq!(700, count_tts!(
87 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
88 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
89 |
90 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
91 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
92 |
93 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
94 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
95 |
96 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
97 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
98 |
99 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
100 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
101 |
102 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
103 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
104 |
105 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
106 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
107 | ));
108 | }
109 | ```
110 |
111 | 可以复制下面的例子运行看看,里面包含递归和反复匹配(代码已隐藏)两种方法。
112 |
113 | ```rust
114 | macro_rules! count_tts {
115 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
116 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
117 | $_k:tt $_l:tt $_m:tt $_n:tt $_o:tt
118 | $_p:tt $_q:tt $_r:tt $_s:tt $_t:tt
119 | $($tail:tt)*)
120 | => {20usize + count_tts!($($tail)*)};
121 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
122 | $_f:tt $_g:tt $_h:tt $_i:tt $_j:tt
123 | $($tail:tt)*)
124 | => {10usize + count_tts!($($tail)*)};
125 | ($_a:tt $_b:tt $_c:tt $_d:tt $_e:tt
126 | $($tail:tt)*)
127 | => {5usize + count_tts!($($tail)*)};
128 | ($_a:tt
129 | $($tail:tt)*)
130 | => {1usize + count_tts!($($tail)*)};
131 | () => {0usize};
132 | }
133 |
134 | // 可试试“反复替代”的方式计数
135 | // --snippet--
136 | # // macro_rules! replace_expr {
137 | # // ($_t:tt $sub:expr) => {
138 | # // $sub
139 | # // };
140 | # // }
141 | # //
142 | # // macro_rules! count_tts {
143 | # // ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
144 | # // }
145 |
146 | fn main() {
147 | assert_eq!(2500,
148 | count_tts!(
149 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
150 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
151 |
152 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
153 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
154 |
155 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
156 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
157 |
158 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
159 | ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
160 |
161 | // --snippet--
162 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
163 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
164 | #
165 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
166 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
167 | #
168 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
169 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
170 | #
171 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
172 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
173 | #
174 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
175 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
176 | #
177 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
178 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
179 | #
180 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
181 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
182 | #
183 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
184 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
185 | #
186 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
187 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
188 | #
189 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
190 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
191 | #
192 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
193 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
194 | #
195 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
196 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
197 | #
198 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
199 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
200 | #
201 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
202 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
203 | #
204 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
205 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
206 | #
207 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
208 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
209 | #
210 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
211 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
212 | #
213 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
214 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
215 | #
216 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
217 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
218 | #
219 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
220 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
221 | #
222 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
223 | # ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
224 | #
225 | // 默认的递归限制让改进的递归代码也无法继续下去了
226 | // 反复替换的代码还能够运行,但明显效率不会很高
227 | // ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
228 | // ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,
229 | ));
230 | }
231 | ```
232 |
233 | [^recur-limit]:译者注:如果不显式提高 128 的递归限制的话,
234 | 这个例子中,增加匹配分支办法可以处理最多 \\(20 \times 128 = 2560 \\) 个标记。
235 |
236 | ## 切片长度
237 |
238 | 第三种方法,是帮助编译器构建一个深度较小的 AST ,以避免栈溢出。
239 | 可以通过构造数组,并调用其 `len` 方法来做到。(slice length)
240 |
241 | ```rust,editable
242 | macro_rules! replace_expr {
243 | ($_t:tt $sub:expr) => {$sub};
244 | }
245 |
246 | macro_rules! count_tts {
247 | ($($tts:tt)*) => {<[()]>::len(&[$(replace_expr!($tts ())),*])};
248 | }
249 |
250 | fn main() {
251 | assert_eq!(count_tts!(0 1 2), 3);
252 |
253 | const N: usize = count_tts!(0 1 2);
254 | let array = [0; N];
255 | println!("{:?}", array);
256 | }
257 | ```
258 |
259 | 经过测试,这种方法可处理高达 10000 个标记数,可能还能多上不少。[^outdated-slice]
260 |
261 | 而且可以用于常量表达式,比如当作在 `const` 值或定长数组的长度值。[^const-limit]
262 |
263 | 所以基本上此方法是 **首选** 。
264 |
265 | [^outdated-slice]:译者注:这个具体的数据可能也过时了,但这个方法的确是高效的。
266 |
267 | [^const-limit]:译者注:原作时这个方法无法用于常量,现在无此限制。
268 |
269 | ## 枚举计数
270 |
271 | 当你需要统计 **互不相同的标识符** 的数量时,
272 | 可以利用枚举体的
273 | [numeric cast](https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations)
274 | 功能来达到统计成员(即标识符)个数。
275 |
276 | ```rust,editable
277 | macro_rules! count_idents {
278 | ($($idents:ident),* $(,)*) => {
279 | {
280 | #[allow(dead_code, non_camel_case_types)]
281 | enum Idents { $($idents,)* __CountIdentsLast }
282 | const COUNT: u32 = Idents::__CountIdentsLast as u32;
283 | COUNT
284 | }
285 | };
286 | }
287 | #
288 | # fn main() {
289 | # const COUNT: u32 = count_idents!(A, B, C);
290 | # assert_eq!(COUNT, 3);
291 | # }
292 | ```
293 |
294 | 此方法有两大缺陷:
295 | 1. 它仅能被用于数有效的标识符(同时还不能是关键词),而且不允许那些标识符有重复
296 | 2. 不具备卫生性:如果你的末位标识符(在 `__CountIdentsLast`[^__CountIdentsLast] 位置上的标识符)的字面值也是输入之一,
297 | 那么宏调用就会失败,因为 `enum` 中包含重复变量。
298 |
299 | [^__CountIdentsLast]:译者注:`__CountIdentsLast` 只是一个自定义的标识符,重点在于它处于枚举成员的最后一位。
300 |
301 | ## bit twiddling
302 |
303 | 另一个递归方法,但是使用了 位操作 (bit operations) [^YatoRust]:
304 |
305 | ```rust,editable
306 | macro_rules! count_tts {
307 | () => { 0 };
308 | ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 };
309 | ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 };
310 | }
311 | #
312 | # fn main() {
313 | # assert_eq!(count_tts!(0 1 2), 3);
314 | # }
315 | ```
316 |
317 | 这种方法非常聪明。
318 | 只要它是偶数个,就能有效地将其输入减半,
319 | 然后将计数器乘以 2(或者在这种情况下,向左移1位)。
320 | 因为由于前一次左移位,此时最低位必须为 0 ,重复直到我们达到基本规则 `() => 0` 。
321 | 如果输入是奇数个,则从第二个输入开始减半,最终将结果进行 或运算(这等效于加 1)。
322 |
323 | 这样做的好处是,生成计数器的 AST 表达式将以 `O(log(n))` 而不是 `O(n)` 复杂度增长。
324 | 请注意,这仍然可能达到递归限制。
325 |
326 | 让我们手动分析中间的过程:
327 |
328 | ```rust,ignore
329 | count_tts!(0 0 0 0 0 0 0 0 0 0);
330 | ```
331 |
332 | 由于我们的标记树数量为偶数(10),因此该调用将与第三条规则匹配。
333 | 该匹配分支把奇数项的标记树命名给 `$a` ,偶数项的标记树命名成 `$b` ,
334 | 但是只会对奇数项 `$a` 展开,这意味着有效地抛弃所有偶数项,切断了一半的输入。
335 | 因此,调用现在变为:
336 |
337 | ```rust,ignore
338 | count_tts!(0 0 0 0 0) << 1;
339 | ```
340 |
341 | 现在,该调用将匹配第二条规则,因为其输入的令牌树数量为奇数。
342 | 在这种情况下,第一个标记树将被丢弃以再次让输入变成偶数个,
343 | 然后可以在调用中再次进行减半步骤。
344 | 此时,我们可以将奇数时丢弃的一项计数为1,然后再乘以2,因为我们也减半了。
345 |
346 | ```rust,ignore
347 | ((count_tts!(0 0) << 1) | 1) << 1;
348 | ```
349 | ```rust,ignore
350 | ((count_tts!(0) << 1 << 1) | 1) << 1;
351 | ```
352 | ```rust,ignore
353 | (((count_tts!() | 1) << 1 << 1) | 1) << 1;
354 | ```
355 | ```rust,ignore
356 | ((((0 << 1) | 1) << 1 << 1) | 1) << 1;
357 | ```
358 |
359 | 现在,要检查是否正确分析了扩展过程,
360 | 我们可以使用 [`debugging`](../macros/minutiae/debugging.html) 调试工具。
361 | 展开宏后,我们应该得到:
362 |
363 | ```rust,ignore
364 | ((((0 << 1) | 1) << 1 << 1) | 1) << 1;
365 | ```
366 |
367 | 没有任何差错,太棒了!
368 |
369 |
370 | > 译者注:以下内容为译者自行补充这小节提到的调试。
371 | > 注意:我这里使用的加、乘运算与上面提到的位运算是一样的。
372 |
373 | ```rust,editable
374 | #![allow(unused)]
375 | macro_rules! count_tts {
376 | () => { 0 };
377 | ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) *2) + 1 };
378 | ($($a:tt $even:tt)*) => { count_tts!($($a)*) *2 };
379 | }
380 |
381 | fn main() {
382 | count_tts!(0 1 2 3 4 5 6 7 8 9 10);
383 | }
384 | ```
385 |
386 | 调试方法(必须在 nightly 版本下):
387 | 1. 使用编译命令 `cargo rustc -- -Z trace-macros`
388 | 得到:
389 |
390 | ```RUST,ignore
391 | note: trace_macro
392 | --> src/main.rs:9:5
393 | |
394 | 9 | count_tts!(0 1 2 3 4 5 6 7 8 9 10);
395 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
396 | |
397 | = note: expanding `count_tts! { 0 1 2 3 4 5 6 7 8 9 10 }`
398 | = note: to `(count_tts! (1 3 5 7 9) * 2) + 1`
399 | = note: expanding `count_tts! { 1 3 5 7 9 }`
400 | = note: to `(count_tts! (3 7) * 2) + 1`
401 | = note: expanding `count_tts! { 3 7 }`
402 | = note: to `count_tts! (3) * 2`
403 | = note: expanding `count_tts! { 3 }`
404 | = note: to `(count_tts! () * 2) + 1`
405 | = note: expanding `count_tts! { }`
406 | = note: to `0`
407 | ```
408 |
409 | 2. 上面的形式太不简洁,所以使用封装好的工具:[cargo-expand](https://github.com/dtolnay/cargo-expand)。
410 | 使用编译命令 `cargo expand` ,得到:
411 |
412 | ```RUST,ignore
413 | #![feature(prelude_import)]
414 | #![allow(unused)]
415 | #[prelude_import]
416 | use std::prelude::rust_2018::*;
417 | #[macro_use]
418 | extern crate std;
419 | fn main() {
420 | (((((0 * 2) + 1) * 2 * 2) + 1) * 2) + 1;
421 | }
422 | ```
423 |
424 | [^YatoRust]:这种方法的归功于 Reddit 用户
425 | [`YatoRust`](https://www.reddit.com/r/rust/comments/d3yag8/the_little_book_of_rust_macros/) 。
426 |
--------------------------------------------------------------------------------
/src/decl-macros/building-blocks/parsing.md:
--------------------------------------------------------------------------------
1 | # 解析 Rust
2 |
3 | 在有些情况下解析某些 Rust [items] 会很有用。
4 | 这一章会展示一些能够解析 Rust 中更复杂的 items 的宏。
5 |
6 | [items]:https://doc.rust-lang.org/nightly/reference/items.html
7 |
8 | [transcribers]:https://doc.rust-lang.org/nightly/reference/macros-by-example.html
9 |
10 | 这些宏目的不是解析整个 items 语法,而是解析通用、有用的部分,
11 | 解析的方式也不会太复杂。
12 | 也就是说,我们不会涉及解析 *泛型* 之类的东西。
13 |
14 | 重点在于宏的匹配方式 (matchers) ;展开的部分 ( *Reference* 里使用的术语叫做 [transcribers] ),
15 | 仅仅用作例子,不需要特别关心它。
16 |
17 | ## 函数
18 |
19 | ```rust,editable
20 | macro_rules! function_item_matcher {
21 | (
22 |
23 | $( #[$meta:meta] )*
24 | // ^~~~attributes~~~~^
25 | $vis:vis fn $name:ident ( $( $arg_name:ident : $arg_ty:ty ),* $(,)? )
26 | // ^~~~~~~~~~~~~~~~argument list!~~~~~~~~~~~~~~^
27 | $( -> $ret_ty:ty )?
28 | // ^~~~return type~~~^
29 | { $($tt:tt)* }
30 | // ^~~~~body~~~~^
31 | ) => {
32 | $( #[$meta] )*
33 | $vis fn $name ( $( $arg_name : $arg_ty ),* ) $( -> $ret_ty )? { $($tt)* }
34 | }
35 | }
36 |
37 | #function_item_matcher!(
38 | # #[inline]
39 | # #[cold]
40 | # pub fn foo(bar: i32, baz: i32, ) -> String {
41 | # format!("{} {}", bar, baz)
42 | # }
43 | #);
44 | #
45 | # fn main() {
46 | # assert_eq!(foo(13, 37), "13 37");
47 | # }
48 | ```
49 |
50 | 这是一个简单的匹配函数的例子,
51 | 传入宏的函数不能包含 `unsafe`、`async`、泛型和 where 语句。
52 | 如果需要解析这些内容,则最好使用 `proc-macro` (过程宏) 代替。
53 |
54 | 这个例子可以检查函数签名,从中生成一些额外的东西,
55 | 然后再重新返回 (re-emit) 整个函数。
56 | 有点像 `Derive` 过程宏,虽然功能没那么强大,但是是为函数服务的
57 | ( `Derive` 不作用于函数)。
58 |
59 | > 理想情况下,我们对参数捕获宁愿使用 `pat` 分类符,而不是 `ident` 分类符,
60 | 但这里目前不被允许(因为前者的跟随限制,不允许其后使用 `:` )。
61 | 幸好在函数签名里面不常使用模式 ( `pat` ) ,所以这个例子还不错。
62 |
63 | ## 方法
64 |
65 | 有时我们想解析方法 (methods),方法就是通过 `self` 的某种形式指向对象的函数。
66 | 这让事情变得棘手多了。
67 |
68 | > WIP (待完善)
69 |
70 | ## 结构体
71 |
72 | ```rust,editable
73 | macro_rules! struct_item_matcher {
74 | // Unit-Struct
75 | (
76 | $( #[$meta:meta] )*
77 | // ^~~~attributes~~~~^
78 | $vis:vis struct $name:ident;
79 | ) => {
80 | $( #[$meta] )*
81 | $vis struct $name;
82 | };
83 |
84 | // Tuple-Struct
85 | (
86 | $( #[$meta:meta] )*
87 | // ^~~~attributes~~~~^
88 | $vis:vis struct $name:ident (
89 | $(
90 | $( #[$field_meta:meta] )*
91 | // ^~~~field attributes~~~~^
92 | $field_vis:vis $field_ty:ty
93 | // ^~~~~~a single field~~~~~~^
94 | ),*
95 | $(,)? );
96 | ) => {
97 | $( #[$meta] )*
98 | $vis struct $name (
99 | $(
100 | $( #[$field_meta] )*
101 | $field_vis $field_ty
102 | ),*
103 | );
104 | };
105 |
106 | // Named-Struct
107 | (
108 | $( #[$meta:meta] )*
109 | // ^~~~attributes~~~~^
110 | $vis:vis struct $name:ident {
111 | $(
112 | $( #[$field_meta:meta] )*
113 | // ^~~~field attributes~~~!^
114 | $field_vis:vis $field_name:ident : $field_ty:ty
115 | // ^~~~~~~~~~~~~~~~~a single field~~~~~~~~~~~~~~~^
116 | ),*
117 | $(,)? }
118 | ) => {
119 | $( #[$meta] )*
120 | $vis struct $name {
121 | $(
122 | $( #[$field_meta] )*
123 | $field_vis $field_name : $field_ty
124 | ),*
125 | }
126 | }
127 | }
128 |
129 | #struct_item_matcher!(
130 | # #[allow(dead_code)]
131 | # #[derive(Copy, Clone)]
132 | # pub(crate) struct Foo {
133 | # pub bar: i32,
134 | # baz: &'static str,
135 | # qux: f32
136 | # }
137 | #);
138 | #struct_item_matcher!(
139 | # #[derive(Copy, Clone)]
140 | # pub(crate) struct Bar;
141 | #);
142 | #struct_item_matcher!(
143 | # #[derive(Clone)]
144 | # pub(crate) struct Baz (i32, pub f32, String);
145 | #);
146 | #fn main() {
147 | # let _: Foo = Foo { bar: 42, baz: "macros can be nice", qux: 3.14, };
148 | # let _: Bar = Bar;
149 | # let _: Baz = Baz(2, 0.1234, String::new());
150 | #}
151 | ```
152 |
153 | ## 枚举体
154 |
155 | 解析枚举体比解析结构体更复杂一点,所以会用上 [模式][patterns] 这章讨论的技巧:
156 | [`TT` 撕咬机][Incremental TT Muncher] 和 [内用规则][Internal Rules] 。
157 |
158 | 不是重新构造被解析的枚举体,而是只访问枚举体所有的标记 (tokens),
159 | 因为重构枚举体将需要我们再通过 [下推累积][Push Down Accumulator]
160 | 临时组合所有已解析的标记 (tokens) 。
161 |
162 | ```rust,editable
163 | macro_rules! enum_item_matcher {
164 | // tuple variant
165 | (@variant $variant:ident (
166 | $(
167 | $( #[$field_meta:meta] )*
168 | // ^~~~field attributes~~~~^
169 | $field_vis:vis $field_ty:ty
170 | // ^~~~~~a single field~~~~~~^
171 | ),* $(,)?
172 | //∨~~rest of input~~∨
173 | ) $(, $($tt:tt)* )? ) => {
174 |
175 | // process rest of the enum
176 | $( enum_item_matcher!(@variant $( $tt )*); )?
177 | };
178 |
179 | // named variant
180 | (@variant $variant:ident {
181 | $(
182 | $( #[$field_meta:meta] )*
183 | // ^~~~field attributes~~~!^
184 | $field_vis:vis $field_name:ident : $field_ty:ty
185 | // ^~~~~~~~~~~~~~~~~a single field~~~~~~~~~~~~~~~^
186 | ),* $(,)?
187 | //∨~~rest of input~~∨
188 | } $(, $($tt:tt)* )? ) => {
189 | // process rest of the enum
190 | $( enum_item_matcher!(@variant $( $tt )*); )?
191 | };
192 |
193 | // unit variant
194 | (@variant $variant:ident $(, $($tt:tt)* )? ) => {
195 | // process rest of the enum
196 | $( enum_item_matcher!(@variant $( $tt )*); )?
197 | };
198 |
199 | // trailing comma
200 | (@variant ,) => {};
201 | // base case
202 | (@variant) => {};
203 |
204 | // entry point
205 | (
206 | $( #[$meta:meta] )*
207 | $vis:vis enum $name:ident {
208 | $($tt:tt)*
209 | }
210 | ) => {
211 | enum_item_matcher!(@variant $($tt)*);
212 | };
213 | }
214 |
215 | enum_item_matcher!(
216 | #[derive(Copy, Clone)]
217 | pub(crate) enum Foo {
218 | Bar,
219 | Baz,
220 | }
221 | );
222 | enum_item_matcher!(
223 | #[derive(Copy, Clone)]
224 | pub(crate) enum Bar {
225 | Foo(i32, f32),
226 | Bar,
227 | Baz(),
228 | }
229 | );
230 | enum_item_matcher!(
231 | #[derive(Clone)]
232 | pub(crate) enum Baz {}
233 | );
234 |
235 | fn main() {}
236 | ```
237 |
238 | [patterns]:/patterns.html
239 | [Push Down Accumulator]:/patterns/push-down-acc.html
240 | [Internal Rules]:/patterns/internal-rules.html
241 | [Incremental TT Muncher]:/patterns/tt-muncher.html
242 |
--------------------------------------------------------------------------------
/src/decl-macros/macros-methodical.md:
--------------------------------------------------------------------------------
1 | # 思路介绍
2 |
3 | 这一节会介绍 Rust 的[声明宏系统][mbe],解释该系统如何作为整体运作。
4 |
5 | 首先会深入构造语法及其关键部分,然后介绍你至少应该了解的通用信息。
6 |
7 | [mbe]: https://doc.rust-lang.org/reference/macros-by-example.html
8 |
9 | # `macro_rules!`
10 |
11 | 有了前述知识,我们终于可以介绍 `macro_rules!` 了。如前所述,`macro_rules!`
12 | 本身就是一个语法扩展,也就是从技术上说,它并不是 Rust 语法的一部分。它的形式如下:
13 |
14 | ```rust,ignore
15 | macro_rules! $name {
16 | $rule0 ;
17 | $rule1 ;
18 | // …
19 | $ruleN ;
20 | }
21 | ```
22 |
23 | **至少得有一条规则**,而且最后一条规则后面的分号可被省略。规则里你可以使用大/中/小括号:
24 | `{}`、`[]`、`()`[^braces]。每条“规则”都形如:
25 |
26 | ```ignore
27 | ($matcher) => {$expansion}
28 | ```
29 |
30 | [^braces]: 译者注:它们的英文名称有时候很重要,因为如果你不认识英文名称的话,会比较难读懂文档(比如
31 | [`syn`])。braces `{}`、brackets `[]`、parentheses `()`。
32 |
33 | [`syn`]: https://docs.rs/syn/latest/syn/token/index.html#parsing
34 |
35 | 分组符号可以是任意一种括号,但处于习惯,在模式匹配 (matcher) 外侧使用小括号、展开
36 | (expansion 也可以叫做 transcriber) 外侧使用大括号。
37 |
38 | 注意:在规则里选择哪种括号并不会影响宏调用。
39 |
40 | 而且,实际上,你也可以在调用宏时使用这三种中任意一种括号,只不过使用 `{ ... }` 或者 `( ... );`
41 | 的话会有所不同(关注点在于末尾跟随的分号 `;` )。有末尾分号的宏调用**总是**会被解析成一个条目 (item)。
42 |
43 | 如果你好奇的话,`macro_rules!` 的调用将被展开成什么?答案是:空 (nothing)。至少,在 AST
44 | 中它被展开为空。它所影响的是编译器内部的结构,以将该宏注册 (register)
45 | 进去。因此,技术上讲你可以在任何一个空展开合法的位置使用 `macro_rules!`。
46 |
47 | > 译者注:这里提到两种情况,定义声明宏和使用(或者说调用)声明宏。而且,在括号的选取上:
48 | > 1. 定义的规则不关心 `($matcher) => {$expansion}` 中的**外层**括号类型,但 matcher 和 expansion
49 | > 之内的括号属于匹配和展开的内容,所以它们内部使用什么括号取决于你需要什么语法。
50 | > 2. 假如使用 `m!` 这个宏,如果该宏展开成条目,则必须使用 `m! { ... }` 或者 `m!( ... );`;
51 | > 如果该宏展开成表达式,你可以使用 `m! { ... }` 或者 `m!( ... )` 或者 `m![ ... ]`。
52 | > 3. 实际上,定义宏的括号遵循习惯就好,而使用宏的括号用错的话,只需仔细阅读编译器给你的错误信息,和以上第
53 | > 2 点,就知道怎么改了。
54 |
55 | ## 匹配
56 |
57 | 当一个宏被调用时,`macro_rules!` 解释器将按照声明顺序一一检查规则。
58 |
59 | 对每条规则,它都将尝试将输入标记树的内容与该规则的 `matcher` 进行匹配。某个 matcher [^matcher]
60 | 必须与输入**完全**匹配才被认为是一次匹配。
61 |
62 | [^matcher]: 译者注:为了简单起见,我不翻译 matcher 这个术语,它指的是被匹配的部分,也就是声明宏规则的前半段。
63 |
64 | 如果输入与某个 matcher 相匹配,则该调用将替换成相应的展开内容 (`expansion`) ;否则,将尝试匹配下条规则。
65 |
66 | 如果所有规则均匹配失败,则宏展开失败并报错。
67 |
68 | 最简单的例子是空 matcher:
69 |
70 | ```rust,ignore
71 | macro_rules! four {
72 | () => { 1 + 3 };
73 | }
74 | ```
75 |
76 | 当且仅当匹配到空的输入时,匹配成功,即 `four!()`、`four![]` 或 `four!{}` 三种方式调用是匹配成功的 。
77 |
78 | 注意所用的分组标记并**不需要**匹配定义时采用的分组标记,因为实际上分组标记并未传给调用。
79 |
80 | 也就是说,你可以通过 `four![]` 调用上述宏,此调用仍将被视作匹配成功。只有输入的内容才会被纳入匹配考量范围。
81 |
82 | matcher 中也可以包含字面上[^literal]的标记树,这些标记树必须被完全匹配。将整个对应标记树在相应位置写下即可。
83 |
84 | 比如,要匹配标记序列 `4 fn ['spang "whammo"] @_@` ,我们可以这样写:
85 |
86 | ```rust,ignore
87 | macro_rules! gibberish {
88 | (4 fn ['spang "whammo"] @_@) => {...};
89 | }
90 | ```
91 |
92 | 使用 `gibberish!(4 fn ['spang "whammo"] @_@])` 即可成功匹配和调用。
93 |
94 | 你能写出什么标记树,就可以使用什么标记树。
95 |
96 | [^literal]: 译者注:这里不是指 Rust 的“字面值”,而是指不考虑含义的标记,比如这个例子中 `fn` 和 `[]`都不是
97 | Rust 的 [literal] 标记 ([token]),而是 [keyword] 和 [delimiter]
98 | 标记,或者从下面谈到的元变量角度看,它们**可以**被 `ident` 或者 `tt` 分类符捕获。
99 |
100 | [literal]: https://doc.rust-lang.org/reference/tokens.html#literals
101 | [token]: https://doc.rust-lang.org/reference/tokens.html
102 | [delimiter]: https://doc.rust-lang.org/reference/tokens.html#delimiters
103 | [keyword]: https://doc.rust-lang.org/reference/keywords.html
104 |
105 | ## 元变量
106 |
107 | matcher 还可以包含捕获 (captures)。即基于某种通用语法类别来匹配输入,并将结果捕获到元变量 (metavariable)
108 | 中,然后将替换元变量到输出。
109 |
110 | 捕获的书写方式是:先写美元符号 `$`,然后跟一个标识符,然后是冒号 `:`,最后是捕获方式,比如 `$e:expr`。
111 |
112 | 捕获方式又被称作“片段分类符” ([fragment-specifier]),必须是以下一种:
113 |
114 | * [`block`](./minutiae/fragment-specifiers.md#block):一个块(比如一块语句或者由大括号包围的一个表达式)
115 | * [`expr`](./minutiae/fragment-specifiers.md#expr):一个表达式 (expression)
116 | * [`ident`](./minutiae/fragment-specifiers.md#ident):一个标识符 (identifier),包括关键字 (keywords)
117 | * [`item`](./minutiae/fragment-specifiers.md#item):一个条目(比如函数、结构体、模块、`impl` 块)
118 | * [`lifetime`](./minutiae/fragment-specifiers.md#lifetime):一个生命周期注解(比如 `'foo`、`'static`)
119 | * [`literal`](./minutiae/fragment-specifiers.md#literal):一个字面值(比如 `"Hello World!"`、`3.14`、`'🦀'`)
120 | * [`meta`](./minutiae/fragment-specifiers.md#meta):一个元信息(比如 `#[...]` 和 `#![...]` 属性内部的东西)
121 | * [`pat`](./minutiae/fragment-specifiers.md#pat):一个模式 (pattern)
122 | * [`path`](./minutiae/fragment-specifiers.md#path):一条路径(比如 `foo`、`::std::mem::replace`、`transmute::<_, int>`)
123 | * [`stmt`](./minutiae/fragment-specifiers.md#stmt):一条语句 (statement)
124 | * [`tt`](./minutiae/fragment-specifiers.md#tt):单棵标记树
125 | * [`ty`](./minutiae/fragment-specifiers.md#ty):一个类型
126 | * [`vis`](./minutiae/fragment-specifiers.md#vis):一个可能为空的可视标识符(比如 `pub`、`pub(in crate)`)
127 |
128 | [fragment-specifier]: https://doc.rust-lang.org/nightly/reference/macros-by-example.html#metavariables
129 |
130 | 关于片段分类符更深入的描述请阅读本书的[片段分类符](./minutiae/fragment-specifiers.md)一章。
131 |
132 | 比如以下声明宏捕获一个表达式输入到元变量 `$e`:
133 |
134 | ```rust,ignore
135 | macro_rules! one_expression {
136 | ($e:expr) => {...};
137 | }
138 | ```
139 |
140 | 元变量对 Rust 编译器的解析器产生影响,而解析器也会确保元变量总是被“正确无误”地解析。
141 |
142 | `expr` 元变量总是捕获完整且符合 Rust 编译版本的表达式。
143 |
144 | 你可以在有限的情况下同时结合字面上的标记树和元变量。(见 [Metavariables and Expansion Redux] 一节)
145 |
146 | 当元变量已经在 matcher 中确定之后,你只需要写 `$name` 就能引用元变量。比如:
147 |
148 | ```rust,ignore
149 | macro_rules! times_five {
150 | ($e:expr) => { 5 * $e };
151 | }
152 | ```
153 |
154 | 元变量被替换成完整的 AST 节点,这很像宏展开。这也意味着被 `$e`
155 | 捕获的任何标记序列都会被解析成单个完整的表达式。
156 |
157 | 你也可以一个 matcher 中捕获多个元变量:
158 |
159 | ```rust,ignore
160 | macro_rules! multiply_add {
161 | ($a:expr, $b:expr, $c:expr) => { $a * ($b + $c) };
162 | }
163 | ```
164 |
165 | 然后在 expansion 中使用任意次数的元变量:
166 |
167 | ```rust,ignore
168 | macro_rules! discard {
169 | ($e:expr) => {};
170 | }
171 | macro_rules! repeat {
172 | ($e:expr) => { $e; $e; $e; };
173 | }
174 | ```
175 |
176 | 有一个特殊的元变量叫做 [`$crate`] ,它用来指代当前 crate 。
177 |
178 | [Metavariables and Expansion Redux]: ./minutiae/metavar-and-expansion.md
179 | [`$crate`]: ./minutiae/hygiene.md#crate
180 |
181 | ## 反复
182 |
183 | matcher 可以有反复捕获 (repetition),这使得匹配一连串标记 (token)
184 | 成为可能。反复捕获的一般形式为 `$ ( ... ) sep rep`。
185 |
186 | * `$` 是字面上的美元符号标记
187 | * `( ... )` 是被反复匹配的模式,由小括号包围。
188 | * `sep` 是**可选**的分隔标记。它不能是括号或者反复操作符 `rep`。常用例子有 `,` 和 `;` 。
189 | * `rep` 是**必须**的重复操作符。当前可以是:
190 | * `?`:表示最多一次重复,所以此时不能前跟分隔标记。
191 | * `*`:表示零次或多次重复。
192 | * `+`:表示一次或多次重复。
193 |
194 | 反复捕获中可以包含任意其他的有效 matcher,比如字面上的标记树、元变量以及任意嵌套的反复捕获。
195 |
196 | 在 expansion 中,使用被反复捕获的内容时,也采用相同的语法。而且被反复捕获的元变量只能存在于反复语法内。
197 |
198 | 举例来说,下面这个宏将每一个元素转换成字符串:它先匹配零或多个由逗号分隔的表达式,并分别将它们构造成
199 | `Vec` 的表达式。
200 |
201 | ```rust,editable
202 | macro_rules! vec_strs {
203 | (
204 | // 开始反复捕获
205 | $(
206 | // 每个反复必须包含一个表达式
207 | $element:expr
208 | )
209 | // 由逗号分隔
210 | ,
211 | // 0 或多次
212 | *
213 | ) => {
214 | // 在这个块内用大括号括起来,然后在里面写多条语句
215 | {
216 | let mut v = Vec::new();
217 |
218 | // 开始反复捕获
219 | $(
220 | // 每个反复会展开成下面表达式,其中 $element 被换成相应被捕获的表达式
221 | v.push(format!("{}", $element));
222 | )*
223 |
224 | v
225 | }
226 | };
227 | }
228 |
229 | fn main() {
230 | let s = vec_strs![1, "a", true, 3.14159f32];
231 | assert_eq!(s, &["1", "a", "true", "3.14159"]);
232 | }
233 | ```
234 |
235 | 你可以在一个反复语句里面使用多次和多个元变量,只要这些元变量以相同的次数重复。所以下面的宏代码正常运行:
236 |
237 | ```rust,editable
238 | macro_rules! repeat_two {
239 | ($($i:ident)*, $($i2:ident)*) => {
240 | $( let $i: (); let $i2: (); )*
241 | }
242 | }
243 |
244 | fn main () {
245 | repeat_two!( a b c d e f, u v w x y z );
246 | }
247 | ```
248 |
249 | 但是这下面的不能运行:
250 |
251 | ```rust,editable
252 | # macro_rules! repeat_two {
253 | # ($($i:ident)*, $($i2:ident)*) => {
254 | # $( let $i: (); let $i2: (); )*
255 | # }
256 | # }
257 |
258 | fn main() {
259 | repeat_two!( a b c d e f, x y z );
260 | }
261 | ```
262 |
263 | 运行报以下错误:
264 |
265 | ```
266 | error: meta-variable `i` repeats 6 times, but `i2` repeats 3 times
267 | --> src/main.rs:6:10
268 | |
269 | 6 | $( let $i: (); let $i2: (); )*
270 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
271 | ```
272 |
273 | ## 元变量表达式
274 |
275 | > *RFC*: [rfcs#1584](https://github.com/rust-lang/rfcs/blob/master/text/3086-macro-metavar-expr.md)
276 | >
277 | > *Tracking Issue*: [rust#83527](https://github.com/rust-lang/rust/issues/83527)
278 | >
279 | > *Feature*: `#![feature(macro_metavar_expr)]`
280 |
281 | transcriber[^transcriber] 可以包含所谓的元变量表达 (metavariable expressions)。
282 |
283 | 元变量表达式为 transcriber 提供了关于元变量的信息 —— 这些信息是不容易获得的。
284 |
285 | 目前除了 `$$` 表达式外,它们的一般形式都是 `$ { op(...) }`:即除了 `$$` 以外的所有元变量表达式都涉及反复。
286 |
287 | 可以使用以下表达式(其中 `ident` 是所绑定的元变量的名称,而 `depth` 是整型字面值):
288 |
289 | * `${count(ident)}`:最里层反复 `$ident` 的总次数,相当于 `${count(ident, 0)}`
290 | * `${count(ident,depth)}`:第 `depth` 层反复 `$ident` 的次数
291 | * `${index()}`:最里层反复的当前反复的索引,相当于 `${index(0)}`
292 | * `${index(depth)}`:在第 `depth` 层处当前反复的索引,向外计数
293 | * `${length()}`:最里层反复的重复次数,相当于 `${length(0)}`
294 | * `${length(depth)}`:在第 `depth` 层反复的次数,向外计数
295 | * `${ignore(ident)}`:绑定 `$ident` 进行重复,并展开成空
296 | * `$$`:展开为单个 `$`,这会有效地转义 `$` 标记,因此它不会被展开(转写)
297 |
298 | [^transcriber]: 即 expansion,指展开的部分,是每条声明宏规则的后半段。
299 |
300 | ---
301 |
302 |
303 |
304 | 想了解完整的定义语法,可以参考 Rust Reference 书的 [Macros By Example][mbe] 一章。
305 |
306 |
--------------------------------------------------------------------------------
/src/decl-macros/macros-practical-table.html:
--------------------------------------------------------------------------------
1 |
28 |
29 |
Position | 39 |Input | 40 |inits |
41 | recur |
42 |
---|---|---|---|
a[n] = $($inits:expr),+ , ... , $recur:expr
47 | ⌂ |
48 | a[n] = 0, 1, ..., a[n-1] + a[n-2] |
49 | 50 | | 51 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
54 | ⌂ |
55 | [n] = 0, 1, ..., a[n-1] + a[n-2] |
56 | 57 | | 58 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
61 | ⌂ |
62 | n] = 0, 1, ..., a[n-1] + a[n-2] |
63 | 64 | | 65 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
68 | ⌂ |
69 | ] = 0, 1, ..., a[n-1] + a[n-2] |
70 | 71 | | 72 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
75 | ⌂ |
76 | = 0, 1, ..., a[n-1] + a[n-2] |
77 | 78 | | 79 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
82 | ⌂ |
83 | 0, 1, ..., a[n-1] + a[n-2] |
84 | 85 | | 86 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
89 | ⌂ |
90 | 0, 1, ..., a[n-1] + a[n-2] |
91 | 92 | | 93 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
96 | ⌂ ⌂ |
97 | , 1, ..., a[n-1] + a[n-2] |
98 | 0 |
99 | 100 | |
103 | 注意: 这有两个 `⌂` ,因为下个输入标记既能匹配 重复元素间的分隔符逗号,也能匹配 标志重复结束的逗号。宏系统将同时追踪这两种可能,直到决定具体选择为止。 104 | | 105 ||||
a[n] = $($inits:expr),+ , ... , $recur:expr
108 | ⌂ ⌂ |
109 | 1, ..., a[n-1] + a[n-2] |
110 | 0 |
111 | 112 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
115 | |
116 | , ..., a[n-1] + a[n-2] |
117 | 0 , 1 |
118 | 119 | |
122 | 注意:第一个被划掉的记号表明, 123 | 基于上个被消耗的标记,宏系统排除了一项先前存在的可能。 124 | | 125 ||||
a[n] = $($inits:expr),+ , ... , $recur:expr
128 | ⌂ |
129 | ..., a[n-1] + a[n-2] |
130 | 0 , 1 |
131 | 132 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
135 | ⌂ |
136 | , a[n-1] + a[n-2] |
137 | 0 , 1 |
138 | 139 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
142 | ⌂ |
143 | a[n-1] + a[n-2] |
144 | 0 , 1 |
145 | 146 | |
a[n] = $($inits:expr),+ , ... , $recur:expr
149 | ⌂ |
150 | 151 | | 0 , 1 |
152 | a[n-1] + a[n-2] |
153 |
156 | 注意:这一步表明,类似 $recur:expr 157 | 的绑定将消耗一个完整的表达式。 158 | 究竟什么算是一个完整的表达式,将由编译器决定。 159 | 稍后我们会谈到语言其它部分的类似行为。 160 | | 161 |
macro_rules! using_a {
($e:expr) => {
{
let a = 42;
$e
}
}
}
let four = using_a!(a / 10);
28 |
29 | 我们将采用背景色来表示句法上下文。现在,将上述宏调用展开如下:
30 |
31 | let four = { let a = 42; a / 10 };32 | 33 | 首先,回想一下,在展开的期间调用声明宏,实际是空(因为那是一棵待补全的语法树)。 34 | 35 | 其次,如果我们现在就尝试编译上述代码,编译器将报如下错误: 36 | 37 | ```rust,ignore 38 | error[E0425]: cannot find value `a` in this scope 39 | --> src/main.rs:13:21 40 | | 41 | 13 | let four = using_a!(a / 10); 42 | | ^ not found in this scope 43 | ``` 44 | 45 | 注意到宏在展开后背景色(即其句法上下文)发生了改变。 46 | 每处宏展开均赋予其内容一个新的、独一无二的上下文。 47 | 故而,在展开后的代码中实际上存在 *两个* 不同的 `a`,它们分别有不同的句法上下文。 48 | 即,第一个 `a` 与第二个 `a` 并不相同,即使它们便看起来很像。 49 | 50 | 也就是说,被替换进宏展开中的标记仍然 **保持** 着它们原有的句法上下文。 51 | 52 | 因为它们是被传给这宏的,并非这宏本身的一部分。因此,我们作出如下修改: 53 | 54 |
macro_rules! using_a {
($a:ident, $e:expr) => {
{
let $a = 42;
$e
}
}
}
let four = using_a!(a, a / 10);
55 |
56 | 展开如下:
57 |
58 | let four = { let a = 42; a / 10 };59 | 60 | 因为只用了一个 `a`(显然 `a` 在此处是局部变量),编译器将欣然接受此段代码。 61 | 62 | ## `$crate` 元变量 63 | 64 | 当声明宏需要其定义所在的 (defining) crate 65 | 的其他 items 时,由于“卫生性”,我们需要使用 `$crate` 元变量。 66 | 67 | 这个特殊的元变量所做的事情是,它展开成宏所定义的 crate 的绝对路径。 68 | 69 | ```rust,ignore 70 | //// 在 `helper_macro` crate 里定义 `helped!` 和 `helper!` 宏 71 | #[macro_export] 72 | macro_rules! helped { 73 | // () => { helper!() } // 这行可能导致 `helper` 不在作用域的错误 74 | () => { $crate::helper!() } 75 | } 76 | 77 | #[macro_export] 78 | macro_rules! helper { 79 | () => { () } 80 | } 81 | 82 | //// 在另外的 crate 中使用这两个宏 83 | // 注意:`helper_macro::helper` 并没有导入进来 84 | use helper_macro::helped; 85 | 86 | fn unit() { 87 | // 这个宏能运行通过,因为 `$crate` 正确地展开成 `helper_macro` crate 的路径(而不是使用者的路径) 88 | helped!(); 89 | } 90 | ``` 91 | 92 | 请注意,`$crate` 用在指明非宏的 items 时,它必须和完整且有效的模块路径一起使用。如下: 93 | 94 | ```rust 95 | pub mod inner { 96 | #[macro_export] 97 | macro_rules! call_foo { 98 | () => { $crate::inner::foo() }; 99 | } 100 | 101 | pub fn foo() {} 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/identifiers.md: -------------------------------------------------------------------------------- 1 | # 非标识符的“标识符” 2 | 3 | > 译者注:这里需要记住的重点是 4 | > 5 | > 1. `self` 可以使用 [`ident`] 或者 [`tt`] 分类符来匹配; 6 | > 7 | > 2. `_` 只能在模式中使用,不能用 [`ident`] 分类符匹配,而是用 [`pat`] 或者 8 | > [`tt`] 分类符匹配。 9 | 10 | 有两个标记,当你最终撞见时,很有可能认为它们是标识符 ([`ident`]),但实际上它们不是。 11 | 12 | 然而正是这些标记,在某些情况下又的确是标识符。 13 | 14 | ## `self` 15 | 16 | 第一个是 `self`。毫无疑问,它是一个 **关键词** ([keyword])。在一般的 Rust 17 | 代码中,不可能出现把它解读成标识符的情况;但在宏中这种情况则有可能发生: 18 | 19 | [keyword]: https://doc.rust-lang.org/reference/keywords.html 20 | 21 | ```rust,editable 22 | macro_rules! what_is { 23 | (self) => {"the keyword `self`"}; 24 | ($i:ident) => {concat!("the identifier `", stringify!($i), "`")}; 25 | } 26 | 27 | macro_rules! call_with_ident { 28 | ($c:ident($i:ident)) => {$c!($i)}; 29 | } 30 | 31 | fn main() { 32 | println!("{}", what_is!(self)); 33 | println!("{}", call_with_ident!(what_is(self))); 34 | } 35 | ``` 36 | 37 | 上述代码的输出将是: 38 | 39 | ```text 40 | the keyword `self` 41 | the keyword `self` 42 | ``` 43 | 44 | 但这说不通啊!`call_with_ident!` 要求一个标识符,而且它的确匹配到了,还成功替换了!所以, 45 | `self` 同时是一个关键词,但又不是。你可能会想,好吧,但这鬼东西哪里重要呢?看看这个: 46 | 47 | ```rust,editable 48 | macro_rules! make_mutable { 49 | ($i:ident) => {let mut $i = $i;}; 50 | } 51 | 52 | struct Dummy(i32); 53 | 54 | impl Dummy { 55 | fn double(self) -> Dummy { 56 | make_mutable!(self); 57 | self.0 *= 2; 58 | self 59 | } 60 | } 61 | 62 | fn main() { 63 | println!("{:?}", Dummy(4).double().0); 64 | } 65 | ``` 66 | 67 | 编译它会失败,并报错: 68 | 69 | ```rust,ignore 70 | error: `mut` must be followed by a named binding 71 | --> src/main.rs:2:24 72 | | 73 | 2 | ($i:ident) => {let mut $i = $i;}; 74 | | ^^^^^^ help: remove the `mut` prefix: `self` 75 | ... 76 | 9 | make_mutable!(self); 77 | | -------------------- in this macro invocation 78 | | 79 | = note: `mut` may be followed by `variable` and `variable @ pattern` 80 | ``` 81 | 82 | 所以说,宏在匹配的时候,会欣然把 `self` 当作标识符接受,进而允许你把 `self` 83 | 带到那些实际上没办法使用的情况中去。但是,也成吧,既然得同时记住 `self` 84 | 既是关键词又是标识符,那下面这个讲道理应该可行,对吧? 85 | 86 | ```rust,editable 87 | macro_rules! make_self_mutable { 88 | ($i:ident) => {let mut $i = self;}; 89 | } 90 | 91 | struct Dummy(i32); 92 | 93 | impl Dummy { 94 | fn double(self) -> Dummy { 95 | make_self_mutable!(mut_self); 96 | mut_self.0 *= 2; 97 | mut_self 98 | } 99 | } 100 | 101 | fn main() { 102 | println!("{:?}", Dummy(4).double().0); 103 | } 104 | ``` 105 | 106 | 实际上也不行,编译错误变成: 107 | 108 | ```rust,ignore 109 | error[E0424]: expected value, found module `self` 110 | --> src/main.rs:2:33 111 | | 112 | 2 | ($i:ident) => {let mut $i = self;}; 113 | | ^^^^ `self` value is a keyword only available in methods with a `self` parameter 114 | ... 115 | 8 | / fn double(self) -> Dummy { 116 | 9 | | make_self_mutable!(mut_self); 117 | | | ----------------------------- in this macro invocation 118 | 10 | | mut_self.0 *= 2; 119 | 11 | | mut_self 120 | 12 | | } 121 | | |_____- this function has a `self` parameter, but a macro invocation can only access identifiers it receives from parameters 122 | | 123 | ``` 124 | 125 | 这同样也说不通。这简直就像是在抱怨说,它看见的两个 `self` 不是同一个 `self` 126 | ... 就搞得像关键词 `self` 就像标识符一样,也有卫生性。 127 | 128 | ```rust,editable 129 | macro_rules! double_method { 130 | ($body:expr) => { 131 | fn double(mut self) -> Dummy { 132 | $body 133 | } 134 | }; 135 | } 136 | 137 | struct Dummy(i32); 138 | 139 | impl Dummy { 140 | double_method! {{ 141 | self.0 *= 2; 142 | self 143 | }} 144 | } 145 | 146 | fn main() { 147 | println!("{:?}", Dummy(4).double().0); 148 | } 149 | ``` 150 | 151 | 还是报同样的错。那这个如何: 152 | 153 | ```rust,editable 154 | macro_rules! double_method { 155 | ($self_:ident, $body:expr) => { 156 | fn double(mut $self_) -> Dummy { 157 | $body 158 | } 159 | }; 160 | } 161 | 162 | struct Dummy(i32); 163 | 164 | impl Dummy { 165 | double_method! {self, { 166 | self.0 *= 2; 167 | self 168 | }} 169 | } 170 | 171 | fn main() { 172 | println!("{:?}", Dummy(4).double().0); 173 | } 174 | ``` 175 | 176 | 终于管用了。所以说,`self` 是关键词,但如果想它变成标识符,那么同时也能是一个标识符。 177 | 178 | 那么,相同的道理对类似的其它东西有用吗? 179 | 180 | ## `_` 181 | 182 | ```rust,editable 183 | macro_rules! double_method { 184 | ($self_:ident, $body:expr) => { 185 | fn double($self_) -> Dummy { 186 | $body 187 | } 188 | }; 189 | } 190 | 191 | struct Dummy(i32); 192 | 193 | impl Dummy { 194 | double_method! {_, 0} 195 | } 196 | 197 | fn main() { 198 | println!("{:?}", Dummy(4).double().0); 199 | } 200 | ``` 201 | 202 | ```rust,ignore 203 | error: no rules expected the token `_` 204 | --> src/main.rs:12:21 205 | | 206 | 1 | macro_rules! double_method { 207 | | -------------------------- when calling this macro 208 | ... 209 | 12 | double_method! {_, 0} 210 | | ^ no rules expected this token in macro call 211 | ``` 212 | 213 | 哈,当然不行。即便它 **如同** `self` 一样从定义上讲是标识符,但 [`_`][_] 214 | 在模式以及表达式中是一个合法的 (valid) 关键词,而不是一个标识符。 215 | 216 | [_]: https://doc.rust-lang.org/reference/patterns.html#wildcard-pattern 217 | 218 | --- 219 | 220 | 你可能觉得,既然 `_` 在模式中有效,那换成 `$self_:pat` 是不是就能一石二鸟了呢? 221 | 222 | 可惜了,也不行,因为 `self` 不是一个有效的模式。 223 | 224 | 如果你真想同时匹配这两个标记,仅有的办法是换用 `tt` 来匹配。 225 | 226 | [`tt`]:./fragment-specifiers.html#tt 227 | [`ident`]:./fragment-specifiers.html#ident 228 | [`pat`]:./fragment-specifiers.html#pat 229 | [片段分类符]:./fragment-specifiers.html 230 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/import-export.md: -------------------------------------------------------------------------------- 1 | # 导入/导出 2 | 3 | 在 Rust 的 2015 和 2018 版本中,导入 `macro_rules!` 宏是不一样的。 4 | 仍然建议阅读这两部分,因为 2018 版使用的结构在 2015 版中做出了解释。 5 | 6 | ## 2015 版本 7 | 8 | ### `#[macro_use]` 9 | 10 | [作用域][scoping chapter] 一章中介绍的 `#[macro_use]` 属性 11 | 适用于模块或者 `external crates` 。例如: 12 | 13 | ```rust 14 | #[macro_use] 15 | mod macros { 16 | macro_rules! X { () => { Y!(); } } 17 | macro_rules! Y { () => {} } 18 | } 19 | 20 | X!(); 21 | # 22 | # fn main() {} 23 | ``` 24 | 25 | ### `#[macro_export]` 26 | 27 | 可通过 `#[macro_export]` 将宏从当前`crate`导出。注意,这种方式 **无视** 所有可见性设定。 28 | 29 | 定义 lib 包 `macs` 如下: 30 | 31 | ```rust,ignore 32 | mod macros { 33 | #[macro_export] macro_rules! X { () => { Y!(); } } 34 | #[macro_export] macro_rules! Y { () => {} } 35 | } 36 | 37 | // X! 和 Y! 并非在此处定义的,但它们 **的确** 被导出了(在此处可用) 38 | // 即便 `macros` 模块是私有的 39 | ``` 40 | 41 | 下面(在使用 `macs` lib 的 crate 中)的代码会正常工作: 42 | 43 | ```rust,ignore 44 | X!(); // X 在当前 crate 中被定义 45 | #[macro_use] extern crate macs; // 从 `macs` 中导入 X 46 | X!(); // 这里的 X 是最新声明的 X,即 `macs` crate 中导入的 X 47 | # 48 | # fn main() {} 49 | ``` 50 | 51 | 正如 [作用域][scoping chapter] 一章所说,`#[macro_use]` 作用于 `extern crate` 时, 52 | 会强制把导出的宏提到 crate 的顶层模块(根模块),所以这里无须使用 `macs::macros` 路径。 53 | 54 | > 注意:只有在根模组中,才可将 `#[macro_use]` 用于 `extern crate`。 55 | 56 | 在从 `extern crate` 导入宏时,可显式控制导入 **哪些** 宏。 57 | 从而利用这一特性来限制命名空间污染,或是覆盖某些特定的宏。就像这样: 58 | 59 | ```rust,ignore 60 | // 只导入 `X!` 这一个宏 61 | #[macro_use(X)] extern crate macs; 62 | 63 | // X!(); // X! 已被定义,但 Y! 未被定义。X 与 Y 无关系。 64 | 65 | macro_rules! Y { () => {} } 66 | 67 | X!(); // X 和 Y 都被定义 68 | 69 | fn main() {} 70 | ``` 71 | 72 | 当导出宏时,常常出现的情况是,宏定义需要其引用所在 `crate` 内的非宏符号。 73 | 由于 `crate` 可能被重命名等,我们可以使用一个特殊的替换变量 [`$crate`] 。 74 | 它总将被扩展为宏定义所在的 `crate` 的绝对路径(比如 `:: macs` )。 75 | 76 | 如果你的编译器版本小于 1.30(即 2018 版之前),那么这招并不适用于宏。 77 | 也就是说,你没办法采用类似 `$crate::Y!` 的代码来引用自己 `crate` 里的定义的宏。 78 | 这表示结合 `#[macro_use]` 来选择性导入会无法保证某个名称的宏在另一个 crate 导入同名宏时依然可用。 79 | 80 | 推荐的做法是,在引用非宏名称时,总是采用绝对路径。 81 | 这样可以最大程度上避免冲突,包括跟标准库中名称的冲突。 82 | 83 | [`$crate`]:./hygiene.html#crate-元变量 84 | 85 | ## 2018 版本 86 | 87 | 2018 版本让使用 `macro_rules!` 宏更简单。 88 | 因为新版本设法让 Rust 中某些特殊的东西更像正常的 items 。 89 | 这意味着我们能以命名空间的方式正确导入和使用宏! 90 | 91 | 因此,不必使用 `#[macro_use]` 来导入 来自 extern crate 导出的宏 到全局命名空间, 92 | 现在我们这样做就好了: 93 | 94 | ```rust,ignore 95 | use some_crate::some_macro; 96 | 97 | fn main() { 98 | some_macro!("hello"); 99 | // as well as 100 | some_crate::some_other_macro!("macro world"); 101 | } 102 | ``` 103 | 104 | 可惜,这只适用于导入外部 crate 的宏; 105 | 如果你使用在自己 crate 定义的 `macro_rules!` 宏, 106 | 那么依然需要把 `#[macro_use]` 添加到宏所定义的模块上来引入模块里面的宏。 107 | 因而 [作用域规则][scoping chapter] 就像之前谈论的那样生效。 108 | 109 | > [`$crate`] 前缀(元变量)在 2018 版中可适用于任何东西, 110 | > 在 1.31 版之后,宏 和类似 item 的东西都能用 `$crate` 导入了。 111 | 112 | [scoping chapter]:./scoping.html 113 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/metavar-and-expansion.md: -------------------------------------------------------------------------------- 1 | # 再谈元变量与宏展开 2 | 3 | ## 书写宏规则的顺序 4 | 5 | 一旦语法分析器开始消耗标记以匹配某捕获,整个过程便 **无法停止或回溯** 。 6 | 这意味着,无论输入是什么样的,下面这个宏的第二项规则将永远无法被匹配到: 7 | 8 | ```rust,editable 9 | macro_rules! dead_rule { 10 | ($e:expr) => { ... }; 11 | ($i:ident +) => { ... }; 12 | } 13 | 14 | fn main() { 15 | dead_rule!(x+); 16 | } 17 | ``` 18 | 19 | 考虑当以 `dead_rule!(x+)` 形式调用此宏时,将会发生什么。 20 | 解析器将从第一条规则开始试图进行匹配:它试图将输入解析为一个表达式。 21 | 第一个标记 `x` 作为表达式是有效的,第二个标记——作为二元加符号 `+` 的节点——在表达式中也是有效的。 22 | 23 | 由此你可能会以为,由于输入中并不包含二元加号 `+` 的右侧元素, 24 | 分析器将会放弃尝试这一规则,转而尝试下一条规则。 25 | 实则不然:分析器将会 panic 并终止整个编译过程,最终返回一个语法错误。 26 | 27 | 由于分析器的这一特点,下面这点尤为重要: 28 | 一般而言,在书写宏规则时,**应从最具体的开始写起,依次写直到最不具体的** 。 29 | 30 | ## 片段分类符的跟随限制 31 | 32 | 为防止将来的语法变动影响宏输入的解析方式, 33 | `macro_rules!` 对紧接元变量后的内容施加了限制。 34 | 在 Rust 1.52 中,能够紧跟片段分类符后面的内容具有如下限制[^Follow-set Ambiguity Restrictions]: 35 | 36 | * [`stmt`] 和 [`expr`]:`=>`、`,`、`;` 之一 37 | * [`pat`]:`=>`、`,`、`=`、`if`、`in` 之一[^pat-edition] 38 | * [`pat_param`]:`=>`、`,`、`=`、`|`、`if`、`in` 之一 39 | * [`path`] 和 [`ty`]:`=>`、`,`、`=`、`|`、`;`、`:`、`>`、`>>`、`[`、`{`、`as`、`where` 之一; 40 | 或者 [`block`] 型的元变量 41 | * [`vis`]:`,`、除了 `priv` 之外的标识符、任何以类型开头的标记、 42 | [`ident`] 或 [`ty`] 或 [`path`] 型的元变量 43 | * 其他片段分类符所跟的内容无限制 44 | 45 | [^pat-edition]: 使用 2021 edition 之前的 Rust,`pat` 依然可以跟随 `|`。 46 | 47 | 反复匹配的情况也遵循这些限制[^Follow-set Ambiguity Restrictions],也就是说: 48 | 49 | 1. 如果一个重复操作符(`*` 或 `+`)能让一类元变量重复数次, 50 | 那么反复出现的内容就是这类元变量,反复结束之后所接的内容遵照上面的限制。 51 | 52 | 2. 如果一个重复操作符(`*` 或 `?`)让一类元变量重复零次, 53 | 那么元变量之后的内容遵照上面的限制。 54 | 55 | [^Follow-set Ambiguity Restrictions]: 内容来自于 *Reference* 56 | [follow-set-ambiguity-restrictions](https://doc.rust-lang.org/reference/macros-by-example.html#follow-set-ambiguity-restrictions) 一节。 57 | 58 | ## 编译器拒绝模糊的规则 59 | 60 | 解析器不会预先运行代码,这意味着如果编译器不能一次就唯一地确定如何解析宏调用, 61 | 那么编译器就带着模糊的报错信息而终止运行。 62 | 一个触发终止运行的例子是: 63 | 64 | ```rust,editable 65 | macro_rules! ambiguity { 66 | ($($i:ident)* $i2:ident) => { }; 67 | } 68 | 69 | // error: 70 | // local ambiguity: multiple parsing options: built-in NTs ident ('i') or ident ('i2'). 71 | fn main() { ambiguity!(an_identifier); } 72 | ``` 73 | 74 | 编译器不会提前看到传入的标识符之后是不是一个 `)`,如果提前看到的话就会解析正确。 75 | 76 | ## 不基于标记的代换 77 | 78 | 关于代换元变量 (substitution,这里指把已经进行宏解析的 token 再次传给宏) , 79 | 常常让人惊讶的一面是,尽管 **很像** 是根据标记 (token) 进行代换的,但事实并非如此 80 | ——代换基于已经解析的 AST 节点。 81 | 82 | 思考下面的例子: 83 | 84 | ```rust,editable 85 | macro_rules! capture_then_match_tokens { 86 | ($e:expr) => {match_tokens!($e)}; 87 | } 88 | 89 | macro_rules! match_tokens { 90 | ($a:tt + $b:tt) => {"got an addition"}; 91 | (($i:ident)) => {"got an identifier"}; 92 | ($($other:tt)*) => {"got something else"}; 93 | } 94 | 95 | fn main() { 96 | println!("{}\n{}\n{}\n", 97 | match_tokens!((caravan)), 98 | match_tokens!(3 + 6), 99 | match_tokens!(5)); 100 | println!("{}\n{}\n{}", 101 | capture_then_match_tokens!((caravan)), 102 | capture_then_match_tokens!(3 + 6), 103 | capture_then_match_tokens!(5)); 104 | } 105 | ``` 106 | 107 | 其结果: 108 | 109 | ```text 110 | got an identifier 111 | got an addition 112 | got something else 113 | 114 | got something else 115 | got something else 116 | got something else 117 | ``` 118 | 119 | 通过解析已经传入 AST 节点的输入,代换的结果变得 *很稳定*:你再也无法检查其内容了, 120 | 也不再匹配内容。 121 | 122 | 另一个例子可能也会很令人困惑: 123 | 124 | ```rust,editable 125 | macro_rules! capture_then_what_is { 126 | (#[$m:meta]) => {what_is!(#[$m])}; 127 | } 128 | 129 | macro_rules! what_is { 130 | (#[no_mangle]) => {"no_mangle attribute"}; 131 | (#[inline]) => {"inline attribute"}; 132 | ($($tts:tt)*) => {concat!("something else (", stringify!($($tts)*), ")")}; 133 | } 134 | 135 | fn main() { 136 | println!( 137 | "{}\n{}\n{}\n{}", 138 | what_is!(#[no_mangle]), 139 | what_is!(#[inline]), 140 | capture_then_what_is!(#[no_mangle]), 141 | capture_then_what_is!(#[inline]), 142 | ); 143 | } 144 | ``` 145 | 146 | 结果是: 147 | 148 | ```text 149 | no_mangle attribute 150 | inline attribute 151 | something else (#[no_mangle]) 152 | something else (#[inline]) 153 | ``` 154 | 155 | 避免这个意外情况的唯一方式就是使用 [`tt`]、[`ident`] 或者 [`lifetime`] 分类符。 156 | 每当你捕获到除此之外的分类符,结果将只能被用于直接输出。 157 | 比如这里使用的 `stringify!`[^stringify],它是一条内置于编译器的语法拓展 158 | ([查看源码可知](https://doc.rust-lang.org/src/core/macros/mod.rs.html#974-978)), 159 | 将所有输入标记结合在一起,作为单个字符串输出。 160 | 161 | 162 | [`item`]:./fragment-specifiers.html#item 163 | [`block`]:./fragment-specifiers.html#block 164 | [`stmt`]:./fragment-specifiers.html#stmt 165 | [`pat`]:./fragment-specifiers.html#pat 166 | [`expr`]:./fragment-specifiers.html#expr 167 | [`ty`]:./fragment-specifiers.html#ty 168 | [`ident`]:./fragment-specifiers.html#ident 169 | [`path`]:./fragment-specifiers.html#path 170 | [`tt`]:./fragment-specifiers.html#tt 171 | [`meta`]:./fragment-specifiers.html#meta 172 | [`lifetime`]:./fragment-specifiers.html#lifetime 173 | [`vis`]:./fragment-specifiers.html#vis 174 | [`literal`]:./fragment-specifiers.html#literal 175 | 176 | 177 | [^stringify]:这里未包含原作对 `stringify!` 用于替换 (substitution) 场景的 [解读](https://danielkeep.github.io/tlborm/book/mbe-min-captures-and-expansion-redux.html),因为那个例子的结果有些变化。 178 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/metavar-expr.md: -------------------------------------------------------------------------------- 1 | # 元变量表达式 2 | 3 | > *RFC*: [rfcs#1584](https://github.com/rust-lang/rfcs/blob/master/text/3086-macro-metavar-expr.md) 4 | > *Tracking Issue*: [rust#83527](https://github.com/rust-lang/rust/issues/83527) 5 | > *Feature*: `#![feature(macro_metavar_expr)]` 6 | 7 | > 注意:示例代码片段非常简单,只试图展示它们是如何工作的。 8 | > 关于这些元变量表达式,如果你认为你有合适的、单独使用的小片段,请提交它们! 9 | 10 | 正如在 [思路][methodical] 中提到的,Rust 有一些特殊的元变量表达式(以下简称表达式):transcriber[^transcribe] 11 | 可以使用这些表达式来获取有关元变量的信息。如果没有这些表达式,它们所提供的信息就很难甚至不可能获得。 12 | 13 | 本章将结合用例对它们进行更深入的介绍。 14 | 15 | * [`$$`](#dollar-dollar-) 16 | * [`${count($ident, depth)}`](#countident-depth) 17 | * [`${index(depth)}`](#indexdepth) 18 | * [`${len(depth)}`](#lengthdepth) 19 | * [`${ignore(ident)}`](#ignoreident) 20 | 21 | [methodical]: ../macros-methodical.md 22 | [^transcribe]: 译者注:在专业的讨论中,尤其涉及元变量表达式,常用 transcribe(r) 一词而不使用 expand (expansion)。 23 | 24 | ## Dollar Dollar (`$$`) 25 | 26 | `$$` 表达式展开为单个 `$`,实际上使其成为转义的 `$`。这让声明宏宏生成新的声明宏。 27 | 28 | 因为以前的声明宏将无法转义 `$`,所以无法使用元变量、重复和元变量表达式。例如以下代码片段中不使用 `$$`,就无法使用 `bar!`: 29 | 30 | ```rust 31 | macro_rules! foo { 32 | () => { 33 | macro_rules! bar { 34 | ( $( $any:tt )* ) => { $( $any )* }; 35 | // ^^^^^^^^^^^ error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth 36 | } 37 | }; 38 | } 39 | 40 | foo!(); 41 | # fn main() {} 42 | ``` 43 | 44 | 问题很明显, `foo!` 的 transcriber 看到有反复捕获的意图,并试图反复捕获,但它的作用域中没有 `$any` 45 | 元变量,这导致它产生错误。有了 `$$`,我们就可以解决这个问题[^foo-bar],因为 `foo` 的 transcriber 不再尝试反复捕获。 46 | 47 | ```rust 48 | #![feature(macro_metavar_expr)] 49 | 50 | macro_rules! foo { 51 | () => { 52 | macro_rules! bar { 53 | ( $$( $$any:tt )* ) => { $$( $$any )* }; 54 | } 55 | }; 56 | } 57 | 58 | foo!(); 59 | bar!(); 60 | # fn main() {} 61 | ``` 62 | 63 | [^foo-bar]: 译者注:在没有 `$$` 之前,存在一种技巧绕过这里的问题:你可以使用 `$tt` 捕获 `$` 来进行转义,比如[这样][tt-$]。 64 | 65 | [tt-$]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9ce18fc79ce17c77d20e74f3c46ee13c 66 | 67 | ## `count($ident, depth)` 68 | 69 | `count` 表达式展开成元变量 `$ident` 在给定反复深度的反复次数。 70 | 71 | * `ident` 参数必须是规则作用域中声明的元变量 72 | * `depth ` 参数必须是值小于或等于元变量 `$ident` 出现的最大反复深度的整型字面值 73 | * `count(ident, depth)` 展开成不带后缀的整型字面值标记 74 | * `count(ident)` 是 `count(ident, 0)` 的简写 75 | 76 | ```rust,editable 77 | #![feature(macro_metavar_expr)] 78 | 79 | macro_rules! foo { 80 | ( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => { 81 | println!("count(outer, 0): $outer repeats {} times", ${count($outer)}); 82 | println!("count(inner, 0): The $inner repetition repeats {} times in the outer repetition", ${count($inner, 0)}); 83 | println!("count(inner, 1): $inner repeats {} times in the inner repetitions", ${count($inner, 1)}); 84 | }; 85 | } 86 | 87 | fn main() { 88 | foo! { 89 | outer () ; 90 | outer ( inner , inner ) ; 91 | outer () ; 92 | outer ( inner ) ; 93 | }; 94 | } 95 | ``` 96 | 97 | ## `index(depth)` 98 | 99 | `index(depth)` 表达式展开为给定反复深度下,当前的迭代索引。 100 | 101 | * `depth` 参数表明在第几层反复,这个数字从最内层反复调用表达式开始向外计算 102 | * `index(depth)` 展开成不带后缀的整型字面值标记 103 | * `index()` 是 `index(0)` 的简写 104 | 105 | ```rust,editable 106 | #![feature(macro_metavar_expr)] 107 | 108 | macro_rules! attach_iteration_counts { 109 | ( $( ( $( $inner:ident ),* ) ; )* ) => { 110 | ( $( 111 | $(( 112 | stringify!($inner), 113 | ${index(1)}, // 这指的是外层反复 114 | ${index()} // 这指的是内层反复,等价于 `index(0)` 115 | ),)* 116 | )* ) 117 | }; 118 | } 119 | 120 | fn main() { 121 | let v = attach_iteration_counts! { 122 | ( hello ) ; 123 | ( indices , of ) ; 124 | () ; 125 | ( these, repetitions ) ; 126 | }; 127 | println!("{v:?}"); 128 | } 129 | ``` 130 | 131 | ## `len(depth)` 132 | 133 | `length(depth)` 表达式展开为在给定反复深度的迭代次数。 134 | 135 | * `depth` 参数表示在第几层反复,这个数字从最内层反复调用表达式开始向外计算 136 | * `length(depth)` 展开成不带后缀的整型字面值标记 137 | * `length()` 是 `length(0)` 的简写 138 | 139 | ```rust,editable 140 | #![feature(macro_metavar_expr)] 141 | 142 | macro_rules! lets_count { 143 | ( $( $outer:ident ( $( $inner:ident ),* ) ; )* ) => { 144 | $( 145 | $( 146 | println!( 147 | "'{}' in inner iteration {}/{} with '{}' in outer iteration {}/{} ", 148 | stringify!($inner), ${index()}, ${len()}, 149 | stringify!($outer), ${index(1)}, ${len(1)}, 150 | ); 151 | )* 152 | )* 153 | }; 154 | } 155 | 156 | fn main() { 157 | lets_count!( 158 | many (small , things) ; 159 | none () ; 160 | exactly ( one ) ; 161 | ); 162 | } 163 | ``` 164 | 165 | ## `ignore(ident)` 166 | 167 | `ignore(ident)` 表达式展开为空,这使得在无需实际展开元变量的时候,像元变量反复展开相同次数的某些内容。 168 | 169 | * `ident` 参数必须是规则作用域中声明的元变量 170 | 171 | ```rust,editable 172 | #![feature(macro_metavar_expr)] 173 | 174 | macro_rules! repetition_tuples { 175 | ( $( ( $( $inner:ident ),* ) ; )* ) => { 176 | ($( 177 | $( 178 | ( 179 | ${index()}, 180 | ${index(1)} 181 | ${ignore($inner)} // without this metavariable expression, compilation would fail 182 | ), 183 | )* 184 | )*) 185 | }; 186 | } 187 | 188 | fn main() { 189 | let tuple = repetition_tuples!( 190 | ( one, two ) ; 191 | () ; 192 | ( one ) ; 193 | ( one, two, three ) ; 194 | ); 195 | println!("{tuple:?}"); 196 | } 197 | ``` 198 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/scoping.md: -------------------------------------------------------------------------------- 1 | # 作用域 2 | 3 | > 这部分最新的内容可参考 *Reference* 的 4 | [scoping-exporting-and-importing](https://doc.rust-lang.org/nightly/reference/macros-by-example.html#scoping-exporting-and-importing) 5 | 一节。部分翻译内容引自 *Reference* 6 | [中文版](https://minstrel1977.gitee.io/rust-reference/macros-by-example.html#scoping-exporting-and-importing)。 7 | 8 | 函数式宏的作用域规则可能有一点反直觉。(函数式宏包括声明宏与函数式过程宏。) 9 | 由于历史原因,宏的作用域并不完全像各种程序项那样工作。 10 | 11 | 有两种形式的作用域:文本作用域 (textual scope) 和 基于路径的作用域 (path-based scope)。 12 | 13 | 文本作用域:基于宏在源文件中(定义和使用所)出现的顺序,或是跨多个源文件出现的顺序, 14 | 文本作用域是默认的作用域。 15 | 16 | 基于路径的作用域:与其他程序项作用域的运行方式相同。 17 | 18 | 当声明宏被 非限定标识符(unqualified identifier,非多段路径段组成的限定性路径)调用时, 19 | 会首先在文本作用域中查找。 20 | 如果文本作用域中没有任何结果,则继续在基于路径的作用域中查找。 21 | 22 | 如果宏的名称由路径限定 (qualified with a path) ,则只在基于路径的作用域中查找。 23 | 24 | ## 文本作用域 25 | 26 | ### 宏在子模块中可见 27 | 28 | 与 Rust 语言其余所有部分都不同的是,函数式宏在子模块中仍然可见。 29 | 30 | ```rust,editable 31 | macro_rules! X { () => {}; } 32 | mod a { 33 | X!(); // defined 34 | } 35 | mod b { 36 | X!(); // defined 37 | } 38 | mod c { 39 | X!(); // defined 40 | } 41 | # fn main() {} 42 | ``` 43 | 44 | > 注意:即使子模组的内容处在不同文件中,这些例子中所述的行为仍然保持不变。 45 | 46 | ### 宏在定义之后可见 47 | 48 | 同样与 Rust 语言其余所有部分不同,宏只有在其定义 **之后** 可见。 49 | 下例展示了这一点。同时注意到,它也展示了宏不会“漏出” (leak) 其定义所在的作用域: 50 | 51 | ```rust,editable 52 | mod a { 53 | // X!(); // undefined 54 | } 55 | mod b { 56 | // X!(); // undefined 57 | macro_rules! X { () => {}; } 58 | X!(); // defined 59 | } 60 | mod c { 61 | // X!(); // undefined 62 | } 63 | # fn main() {} 64 | ``` 65 | 66 | 要清楚,即使你把宏移动到外层作用域,词法依赖顺序的规则依然适用。 67 | 68 | ```rust,editable 69 | mod a { 70 | // X!(); // undefined 71 | } 72 | 73 | macro_rules! X { () => {}; } 74 | 75 | mod b { 76 | X!(); // defined 77 | } 78 | mod c { 79 | X!(); // defined 80 | } 81 | # fn main() {} 82 | ``` 83 | 84 | ### 宏与宏之间顺序无关 85 | 86 | 然而对于宏自身来说,这种具有顺序的依赖行为不存在。 87 | 即被调用的宏可以先于调用宏之前声明: 88 | 89 | ```rust,editable 90 | mod a { 91 | // X!(); // undefined 92 | } 93 | 94 | macro_rules! X { () => { Y!(); }; } // 注意这里的代码运行通过 95 | 96 | mod b { 97 | // 注意这里 X 虽然被定义,但是 Y 不被定义,所以不能使用 X 98 | // X!(); // defined, but Y! is undefined 99 | } 100 | 101 | macro_rules! Y { () => {}; } 102 | 103 | mod c { 104 | X!(); // defined, and so is Y! 105 | } 106 | # fn main() {} 107 | ``` 108 | 109 | ### 宏可以被暂时覆盖 110 | 111 | 允许多次定义 `macro_rules!` 宏,最后声明的宏会简单地覆盖 (shadow) 上一个声明的同名宏; 112 | 如果最后声明的宏离开作用域,上一个宏在有效的作用域内还能被使用。 113 | 114 | ```rust,editable 115 | macro_rules! X { (1) => {}; } 116 | X!(1); 117 | macro_rules! X { (2) => {}; } 118 | // X!(1); // Error: no rule matches `1` 119 | X!(2); 120 | 121 | mod a { 122 | macro_rules! X { (3) => {}; } 123 | // X!(2); // Error: no rule matches `2` 124 | X!(3); 125 | } 126 | // X!(3); // Error: no rule matches `3` 127 | X!(2); 128 | 129 | fn main() { } 130 | ``` 131 | ### `#[macro_use]` 属性 132 | 133 | 这个属性放置在宏定义所在的模块前 或者 `extern crate` 语句前。 134 | 135 | 1. 在模块前加上 `#[macro_use]` 属性:导出该模块内的所有宏, 136 | 从而让导出的宏在所定义的模块结束之后依然可用。 137 | 138 | ```rust,editable 139 | mod a { 140 | // X!(); // undefined 141 | } 142 | 143 | #[macro_use] 144 | mod b { 145 | macro_rules! X { () => {}; } 146 | X!(); // defined 147 | } 148 | 149 | mod c { 150 | X!(); // defined 151 | } 152 | # fn main() {} 153 | ``` 154 | 155 | 注意,这可能会产生一些奇怪的后果,因为宏(包括过程宏)中的标识符只有在宏展开的过程中才会被解析。 156 | 157 | ```rust,editable 158 | mod a { 159 | // X!(); // undefined 160 | } 161 | 162 | #[macro_use] 163 | mod b { 164 | macro_rules! X { () => { Y!(); }; } 165 | // X!(); // defined, but Y! is undefined 166 | } 167 | 168 | macro_rules! Y { () => {}; } 169 | 170 | mod c { 171 | X!(); // defined, and so is Y! 172 | } 173 | # fn main() {} 174 | ``` 175 | 176 | 2. 给 `extern crate` 语句加上 `#[macro_use]` 属性: 177 | 把外部 crate 定义且导出的宏引入当前 crate 的根/顶层模块。(当前 crate 使用外部 crate) 178 | 179 | 假设在外部名称为 `mac` 的 crate 中定义了 `X!` 宏,在当前模块: 180 | 181 | ```rust,ignore 182 | //// 这里的 `X!` 与 `Y!` 无关,前者定义于外部 crate,后者定义于当前 crate 183 | 184 | mod a { 185 | // X!(); // defined, but Y! is undefined 186 | } 187 | 188 | macro_rules! Y { () => {}; } 189 | 190 | mod b { 191 | X!(); // defined, and so is Y! 192 | } 193 | 194 | #[macro_use] extern crate macs; 195 | mod c { 196 | X!(); // defined, and so is Y! 197 | } 198 | 199 | # fn main() {} 200 | ``` 201 | 202 | ### 当宏放在函数内 203 | 204 | 前四条作用域规则同样适用于函数。 205 | 至于第五条规则, `#[macro_use]` 属性并不直接作用于函数。 206 | 207 | ```rust,editable 208 | macro_rules! X { 209 | () => { Y!() }; 210 | } 211 | 212 | fn a() { 213 | macro_rules! Y { () => {"Hi!"} } 214 | assert_eq!(X!(), "Hi!"); 215 | { 216 | assert_eq!(X!(), "Hi!"); 217 | macro_rules! Y { () => {"Bye!"} } 218 | assert_eq!(X!(), "Bye!"); 219 | } 220 | assert_eq!(X!(), "Hi!"); 221 | } 222 | 223 | fn b() { 224 | macro_rules! Y { () => {"One more"} } 225 | assert_eq!(X!(), "One more"); 226 | } 227 | # 228 | # fn main() { 229 | # a(); 230 | # b(); 231 | # } 232 | ``` 233 | 234 | ### 关于宏声明的位置 235 | 236 | 由于前述种种规则,一般来说, 237 | 建议将所有应对整个 `crate` 均可见的宏的定义置于根模块的最顶部, 238 | 借以确保它们 **一直** 可用。 239 | 这个建议和适用于在文件 `mod` 定义的宏: 240 | 241 | ```rust,ignore 242 | #[macro_use] 243 | mod some_mod_that_defines_macros; 244 | mod some_mod_that_uses_those_macros; 245 | ``` 246 | 247 | 这里的顺序很重要,因为第二个模块依赖于第一个模块的宏, 248 | 所以改变这两个模块的顺序会无法编译。 249 | 250 | ## 基于路径的作用域 251 | 252 | Rust 的 `macro_rules!` 宏 默认并没有基于路径的作用域。 253 | 254 | 然而,如果这个宏被加上 `#[macro_export]` 属性,那么它就在 crate 的根作用域里被定义, 255 | 而且能直接使用它。 256 | 257 | [导入/导出宏][Import and Export] 一章会更深入地探讨这个属性。 258 | 259 | 260 | [Import and Export]: ./import-export.html 261 | 262 | -------------------------------------------------------------------------------- /src/decl-macros/patterns.md: -------------------------------------------------------------------------------- 1 | # 模式 2 | 3 | 解析和展开模式。 4 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/callbacks.md: -------------------------------------------------------------------------------- 1 | # 回调 2 | 3 | ```rust,editable 4 | macro_rules! call_with_larch { 5 | ($callback:ident) => { $callback!(larch) }; 6 | } 7 | 8 | macro_rules! expand_to_larch { 9 | () => { larch }; 10 | } 11 | 12 | macro_rules! recognize_tree { 13 | (larch) => { println!("#1, the Larch.") }; 14 | (redwood) => { println!("#2, the Mighty Redwood.") }; 15 | (fir) => { println!("#3, the Fir.") }; 16 | (chestnut) => { println!("#4, the Horse Chestnut.") }; 17 | (pine) => { println!("#5, the Scots Pine.") }; 18 | ($($other:tt)*) => { println!("I don't know; some kind of birch maybe?") }; 19 | } 20 | 21 | fn main() { 22 | recognize_tree!(expand_to_larch!()); // 无法直接使用 `expand_to_larch!` 的展开结果 23 | call_with_larch!(recognize_tree); // 回调就是给另一个宏传入宏的名称 (`ident`),而不是宏的结果 24 | } 25 | 26 | // 打印结果: 27 | // I don't know; some kind of birch maybe? 28 | // #1, the Larch. 29 | ``` 30 | 31 | 由于宏展开的机制限制,(至少在最新的 Rust 中) 32 | 不可能做到把一例宏的展开结果作为有效信息提供给另一例宏。 33 | 这为宏的模块化工作施加了难度。 34 | 35 | 使用递归并传递回调 (callbacks) 是条出路。 36 | 作为演示,上例两处宏调用的展开过程如下: 37 | 38 | ```rust,ignore 39 | recognize_tree! { expand_to_larch ! ( ) } 40 | println! { "I don't know; some kind of birch maybe?" } 41 | // ... 42 | 43 | call_with_larch! { recognize_tree } 44 | recognize_tree! { larch } 45 | println! { "#1, the Larch." } 46 | // ... 47 | ``` 48 | 49 | 可以反复匹配 `tt` 来将任意参数转发给回调: 50 | 51 | ```rust,editable 52 | macro_rules! callback { 53 | ($callback:ident( $($args:tt)* )) => { 54 | $callback!( $($args)* ) 55 | }; 56 | } 57 | 58 | fn main() { 59 | callback!(callback(println("Yes, this *was* unnecessary."))); 60 | } 61 | ``` 62 | 63 | 如果需要的话,当然还可以在参数中增加额外的标记 (tokens) 。 64 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/internal-rules.md: -------------------------------------------------------------------------------- 1 | # 内用规则 2 | 3 | ```rust 4 | #[macro_export] 5 | macro_rules! foo { 6 | (@as_expr $e:expr) => {$e}; 7 | 8 | ($($tts:tt)*) => { 9 | foo!(@as_expr $($tts)*) 10 | }; 11 | } 12 | # 13 | # fn main() { 14 | # assert_eq!(foo!(42), 42); 15 | # } 16 | ``` 17 | 18 | 内用规则可用在以下两种情况: 19 | 1. 将多个宏统一为一个; 20 | 2. 通过显式命名宏中调用的规则,来简化 [`TT` “撕咬机”][TT Munchers] 的读写。 21 | 22 | 那么为什么将多个宏统一为一个有用呢? 23 | 主要原因是:在 2015 版本中,未对宏进行空间命名。这导致一个问题——必须重新导出内部定义的所有宏, 24 | 从而污染整个全局宏命名空间;更糟糕的是,宏与其他 crate 的同名宏发生冲突。 25 | 简而言之,这很造成很多麻烦。 26 | 幸运的是,在 rustc版本 >= 1.30 的情况下(即 2018 版本之后), 27 | 这不再是问题了(但是内用规则可以减少不必要声明的宏), 28 | 有关宏导出更多信息,请参阅本书 [导入/导出宏](../macros/minutiae/import-export.md) 。 29 | 30 | 好了,让我们讨论如何利用“内用规则” (internal rules) 来把多个宏统一为一个, 31 | 以及“内用规则”这项技术到底是什么吧。 32 | 33 | 这个例子有两个宏,一个常见的 [`as_expr!`](../building-blocks/ast-coercion.html) 宏 34 | 和 `foo!` 宏,后者使用了前者。如果分开写就是下面的形式: 35 | 36 | ```rust 37 | #[macro_export] 38 | macro_rules! as_expr { ($e:expr) => {$e} } 39 | 40 | #[macro_export] 41 | macro_rules! foo { 42 | ($($tts:tt)*) => { 43 | as_expr!($($tts)*) 44 | }; 45 | } 46 | # 47 | # fn main() { 48 | # assert_eq!(foo!(42), 42); 49 | # } 50 | ``` 51 | 52 | 这当然不是最好的解决办法,正如前面提到的,因为 `as_expr` 污染了全局宏命名空间。 53 | 在这个特定的例子里,`as_expr` 只是一个简单的宏,它只会被使用一次, 54 | 所以,利用内用规则,把它“嵌入”到 `foo` 这个宏里面吧! 55 | 56 | 在 `foo` 仅有的一条规则前面添加一条新匹配模式(新规则), 57 | 这个匹配模式由 `as_expr` 组成(和命名),然后附加上宏的输入参数 `$e:expr` ; 58 | 在展开里填写这个宏被匹配到时具体的内容。从而得到本章开头的代码: 59 | 60 | ```rust,editable 61 | #[macro_export] 62 | macro_rules! foo { 63 | (@as_expr $e:expr) => {$e}; 64 | 65 | ($($tts:tt)*) => { 66 | foo!(@as_expr $($tts)*) 67 | }; 68 | } 69 | # 70 | # fn main() { 71 | # assert_eq!(foo!(42), 42); 72 | # } 73 | ``` 74 | 75 | 可以看到,没有调用 `as_expr` 宏,而是递归调用在参数前放置了特殊标记树的 `foo!(@as_expr $($tts)*)`。 76 | 要是你看得仔细些,你甚至会发现这个模式能好地结合 [`TT` 撕咬机][TT Munchers] ! 77 | 78 | 之所以用 `@` ,是因为在 Rust 1.2 下,该标记尚无任何在前缀位置的用法; 79 | 因此,这个语法定义在当时不会与任何东西撞车。 80 | 如果你想用别的符号或特有前缀都可以(比如试试 `#`、`!` ), 81 | 但 `@` 的用例已被传播开来,因此,使用它可能更容易帮助读者理解你的代码。 82 | 83 | > 注意:`@` 符号很早之前曾作为前缀被用于表示被垃圾回收了的指针, 84 | 那时 Rust 还在采用各种记号代表指针类型。 85 | > 86 | > 而现在的 `@` 只有一种用法: 87 | 将名称绑定至模式中(譬如 `match` 的模式匹配中)。 88 | 在这种用法中它是中缀运算符,与我们的上述用例并不冲突。 89 | 90 | 还有一点要注意,内用规则通常应排在“真正的”规则之前。 91 | 这样做可避免 `macro_rules!` 错把内用规则调用解析成别的东西,比如表达式。 92 | 93 | [TT Munchers]:./tt-muncher.html 94 | 95 | # 性能建议 96 | 97 | 内用规则的一个缺点是它们会增加编译时间。 98 | 99 | 即便最终只有一条规则的宏可以匹配(有效的)宏调用,但编译器必须尝试按顺序匹配所有规则。 100 | 101 | 如果宏有许多规则,则可能会有许多匹配失败的情况,而使用内部规则会增加此类匹配失败的数量。 102 | 103 | 此外,`@as_expr` 方式的标识符使规则变得更长,这略微增加了编译器在匹配时必须做的工作量。 104 | 105 | 因此,为了获得最佳性能,**最好避免使用内部规则**。 106 | 107 | 避免使用它们通常也会使复杂的宏更易于阅读。 108 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/push-down-acc.md: -------------------------------------------------------------------------------- 1 | # 下推累积 2 | 3 | ```rust 4 | macro_rules! init_array { 5 | (@accum (0, $_e:expr) -> ($($body:tt)*)) 6 | => {init_array!(@as_expr [$($body)*])}; 7 | (@accum (1, $e:expr) -> ($($body:tt)*)) 8 | => {init_array!(@accum (0, $e) -> ($($body)* $e,))}; 9 | (@accum (2, $e:expr) -> ($($body:tt)*)) 10 | => {init_array!(@accum (1, $e) -> ($($body)* $e,))}; 11 | (@accum (3, $e:expr) -> ($($body:tt)*)) 12 | => {init_array!(@accum (2, $e) -> ($($body)* $e,))}; 13 | (@as_expr $e:expr) => {$e}; 14 | [$e:expr; $n:tt] => { 15 | { 16 | let e = $e; 17 | init_array!(@accum ($n, e.clone()) -> ()) 18 | } 19 | }; 20 | } 21 | 22 | let strings: [String; 3] = init_array![String::from("hi!"); 3]; 23 | # assert_eq!(format!("{:?}", strings), "[\"hi!\", \"hi!\", \"hi!\"]"); 24 | ``` 25 | 26 | 在 Rust 中,所有宏最终 **必须** 展开为一个完整、有效的句法元素(比如表达式、条目等等)。 27 | 这意味着,不可能定义一个最终展开为残缺构造的宏。 28 | 29 | 有些人可能希望,上例中的宏能被更加直截了当地表述成: 30 | 31 | ```ignore 32 | macro_rules! init_array { 33 | (@accum 0, $_e:expr) => {/* empty */}; 34 | (@accum 1, $e:expr) => {$e}; 35 | (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)}; 36 | (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)}; 37 | [$e:expr; $n:tt] => { 38 | { 39 | let e = $e; 40 | [init_array!(@accum $n, e)] 41 | } 42 | }; 43 | } 44 | ``` 45 | 46 | 他们预期的展开过程如下: 47 | 48 | ```rust,ignore 49 | [init_array!(@accum 3, e)] 50 | [e, init_array!(@accum 2, e)] 51 | [e, e, init_array!(@accum 1, e)] 52 | [e, e, e] 53 | ``` 54 | 55 | 然而,这一思路中,每个中间步骤的展开结果都是一个不完整的表达式。 56 | 即便这些中间结果对外部来说绝不可见,Rust 仍然禁止这种用法。 57 | 58 | 下推累积 (push-down accumulation) 则使我们得以在完全完成之前毋需考虑构造的完整性, 59 | 进而累积构建出我们所需的标记序列。 60 | 本章开头给出的示例中,宏调用的展开过程如下: 61 | 62 | ```rust,ignore 63 | init_array! { String:: from ( "hi!" ) ; 3 } 64 | init_array! { @ accum ( 3 , e . clone ( ) ) -> ( ) } 65 | init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) } 66 | init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) } 67 | init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) } 68 | init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] } 69 | ``` 70 | 71 | 可以修改一下代码,看到每次调用时 `$($body)*` 存储的内容变化: 72 | 73 | ```rust,editable 74 | macro_rules! init_array { 75 | (@accum (0, $_e:expr) -> ($($body:tt)*)) 76 | => {init_array!(@as_expr [$($body)*])}; 77 | (@accum (1, $e:expr) -> ($($body:tt)*)) 78 | => {init_array!(@accum (0, $e) -> ($($body)* $e+3,))}; 79 | (@accum (2, $e:expr) -> ($($body:tt)*)) 80 | => {init_array!(@accum (1, $e) -> ($($body)* $e+2,))}; 81 | (@accum (3, $e:expr) -> ($($body:tt)*)) 82 | => {init_array!(@accum (2, $e) -> ($($body)* $e+1,))}; 83 | (@as_expr $e:expr) => {$e}; 84 | [$e:expr; $n:tt $(; first $init:expr)?] => { 85 | { 86 | let e = $e; 87 | init_array!(@accum ($n, e.clone()) -> ($($init)?,)) 88 | } 89 | }; 90 | } 91 | 92 | fn main() { 93 | let array: [usize; 4] = init_array![0; 3; first 0]; 94 | println!("{:?}", array); 95 | } 96 | ``` 97 | 98 | 根据 [调试](../macros/minutiae/debugging.md) 一章的内容, 99 | 在 nightly Rust 中使用编译命令: 100 | `cargo rustc --bin my-project -- -Z trace-macros` ,即得到以下输出: 101 | 102 | ```rust,ignore 103 | note: trace_macro 104 | --> src/main.rs:20:31 105 | | 106 | 20 | let array: [usize; 4] = init_array![0; 3; first 0]; 107 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 108 | | 109 | = note: expanding `init_array! { 0 ; 3 ; first 0 }` 110 | = note: to `{ let e = 0 ; init_array! (@ accum(3, e.clone()) -> (0,)) }` 111 | = note: expanding `init_array! { @ accum(3, e.clone()) -> (0,) }` 112 | = note: to `init_array! (@ accum(2, e.clone()) -> (0, e.clone() + 1,))` 113 | = note: expanding `init_array! { @ accum(2, e.clone()) -> (0, e.clone() + 1,) }` 114 | = note: to `init_array! (@ accum(1, e.clone()) -> (0, e.clone() + 1, e.clone() + 2,))` 115 | = note: expanding `init_array! { @ accum(1, e.clone()) -> (0, e.clone() + 1, e.clone() + 2,) }` 116 | = note: to `init_array! 117 | (@ accum(0, e.clone()) -> (0, e.clone() + 1, e.clone() + 2, e.clone() + 3,))` 118 | = note: expanding `init_array! { @ accum(0, e.clone()) -> (0, e.clone() + 1, e.clone() + 2, e.clone() + 3,) }` 119 | = note: to `init_array! (@ as_expr [0, e.clone() + 1, e.clone() + 2, e.clone() + 3,])` 120 | = note: expanding `init_array! { @ as_expr [0, e.clone() + 1, e.clone() + 2, e.clone() + 3,] }` 121 | = note: to `[0, e.clone() + 1, e.clone() + 2, e.clone() + 3]` 122 | ``` 123 | 124 | 可以看到,每一步都在累积输出,直到规则完成,给出完整的表达式。 125 | 126 | 上述过程的关键点在于,使用 `$($body:tt)*` 来保存输出中间值, 127 | 而不触发其它解析机制。采用 `($input) -> ($output)` 128 | 的形式仅是出于传统,用以明示此类宏的作用。 129 | 130 | 由于可以存储任意复杂的中间结果, 131 | 下推累积在构建 [`TT` 撕咬机](./tt-muncher.md) 的过程中经常被用到。 132 | 当构造类似于这个例子的宏时,也会结合 [内用规则](./internal-rules.md)。 133 | 134 | # 性能建议 135 | 136 | 下推累积本质上是二次复杂度的。考虑一个包含 100 137 | 个标记树的累加器[^accumulator],每次调用一个标记树: 138 | 139 | * 初始调用将匹配空的累加器 140 | * 调用第一个递归将匹配 1 个标记树累加器 141 | * 调用下一个递归将匹配 2 个标记树累加器 142 | * 以此类推,最多 100 个 143 | 144 | 这是一个典型的二次复杂度模式,长输入会导致宏延长编译时间。 145 | 146 | 此外,TT 撕咬机对其输入也是天生的二次复杂度,所以同时使用 147 | TT 撕咬机和下推累积的宏将是双倍二次的! 148 | 149 | 所有关于 TT 撕咬机的[性能建议](./tt-muncher.md#performance)都适用于下推积累。 150 | 151 | 一般来说,避免过多地使用它们,并尽可能地让它们的简单。 152 | 153 | 最后,确保将累加器放在规则的末尾,而不是开头。 154 | 155 | 这样,如果匹配规则失败,编译器就不必匹配(可能很长的)累加器,从而避免遇到规则中不匹配的部分。这可能会对编译时间产生很大影响。 156 | 157 | [^accumulator]: 译者注:accumulator,即使用下推累积方式编写的声明宏。 158 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/repetition-replacement.md: -------------------------------------------------------------------------------- 1 | # 反复替换 2 | 3 | ```rust,ignore 4 | macro_rules! replace_expr { 5 | ($_t:tt $sub:expr) => {$sub}; 6 | } 7 | ``` 8 | 9 | 在上面代码的模式中,匹配到的重复序列将被直接丢弃, 10 | 仅留用它所带来的长度信息(以及元素的类型信息); 11 | 且原本标记所在的位置将被替换成某种重复元素。 12 | 13 | 举个例子,考虑如何为一个元素多于12个 (Rust 1.2 下的元组元素个数的最大值) 14 | 的 `tuple` 提供默认值。 15 | 16 | ```rust,editable 17 | macro_rules! tuple_default { 18 | ($($tup_tys:ty),*) => { 19 | ( 20 | $( 21 | replace_expr!( 22 | ($tup_tys) 23 | Default::default() 24 | ), 25 | )* 26 | ) 27 | }; 28 | } 29 | 30 | macro_rules! replace_expr { 31 | ($_t:tt $sub:expr) => { 32 | $sub 33 | }; 34 | } 35 | 36 | fn main() { 37 | assert_eq!(tuple_default!(i32, bool, String), 38 | (i32::default(), bool::default(), String::default())); 39 | } 40 | ``` 41 | 42 | > **仅对此例**: 43 | 我们其实可以直接用 `$tup_tys::default()` 。 44 | 45 | 上例中,我们 **并未真正使用** 匹配到的类型。 46 | 实际上,我们把它丢弃了,并用一个表达式重复替代 (repetition replacement) 。 47 | 换句话说,我们实际关心的不是有哪些类型,而是有多少个类型。 48 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/tt-bundling.md: -------------------------------------------------------------------------------- 1 | # `TT` 捆绑 2 | 3 | ```rust,editable 4 | macro_rules! call_a_or_b_on_tail { 5 | ((a: $a:ident, b: $b:ident), call a: $($tail:tt)*) => { 6 | $a(stringify!($($tail)*)) 7 | }; 8 | 9 | ((a: $a:ident, b: $b:ident), call b: $($tail:tt)*) => { 10 | $b(stringify!($($tail)*)) 11 | }; 12 | 13 | ($ab:tt, $_skip:tt $($tail:tt)*) => { 14 | call_a_or_b_on_tail!($ab, $($tail)*) 15 | }; 16 | } 17 | 18 | fn compute_len(s: &str) -> Option