├── .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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | 102 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 120 | 121 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 136 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 150 | 151 | 152 | 153 | 154 | 155 | 161 | 162 | 163 |
PositionInputinitsrecur
a[n] = $($inits:expr),+ , ... , $recur:expr 47 | a[n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 54 | [n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 61 | n] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 68 | ] = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 75 | = 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 82 | 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 89 | 0, 1, ..., a[n-1] + a[n-2]
a[n] = $($inits:expr),+ , ... , $recur:expr 96 | ⌂ ⌂, 1, ..., a[n-1] + a[n-2]0
103 | 注意: 这有两个 `⌂` ,因为下个输入标记既能匹配 重复元素间的分隔符逗号,也能匹配 标志重复结束的逗号。宏系统将同时追踪这两种可能,直到决定具体选择为止。 104 |
a[n] = $($inits:expr),+ , ... , $recur:expr 108 | ⌂ ⌂1, ..., a[n-1] + a[n-2]0
a[n] = $($inits:expr),+ , ... , $recur:expr 115 | , ..., a[n-1] + a[n-2]0, 1
122 | 注意:第一个被划掉的记号表明, 123 | 基于上个被消耗的标记,宏系统排除了一项先前存在的可能。 124 |
a[n] = $($inits:expr),+ , ... , $recur:expr 128 | ..., a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 135 | , a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 142 | a[n-1] + a[n-2]0, 1
a[n] = $($inits:expr),+ , ... , $recur:expr 149 | 0, 1a[n-1] + a[n-2]
156 | 注意:这一步表明,类似 $recur:expr 157 | 的绑定将消耗一个完整的表达式。 158 | 究竟什么算是一个完整的表达式,将由编译器决定。 159 | 稍后我们会谈到语言其它部分的类似行为。 160 |
164 | -------------------------------------------------------------------------------- /src/decl-macros/macros2.md: -------------------------------------------------------------------------------- 1 | # 声明宏 2.0 2 | 3 | > *RFC*: [rfcs#1584](https://github.com/rust-lang/rfcs/blob/master/text/1584-macros.md) 4 | > *Tracking Issue*: [rust#39412](https://github.com/rust-lang/rust/issues/39412) 5 | > *Feature*: `#![feature(decl_macro)]` 6 | 7 | 虽然这还未稳定(或者更确切地说,还远未完成),但有人提议建立一个新的声明宏系统,该系统应该取代 8 | `macro_rules!`,并给其取名为声明宏 2.0、`macro`、`decl_macro` 或者更混乱的名称 `macros-by-example`。 9 | 10 | 本章只是为了快速浏览当前状态,展示如何使用这个宏系统以及它的不同之处。 11 | 12 | 这里所描述的一切都不是最终成型的或完整的,因为它们可能会发生变化。 13 | 14 | ## 语法 15 | 16 | 我们将对前几章中实现的两个宏在 `macro` 和 `macro_rules` 的语法之间进行比较: 17 | 18 | ```rust,editable 19 | #![feature(decl_macro)] 20 | 21 | macro_rules! replace_expr_ { 22 | ($_t:tt $sub:expr) => { $sub } 23 | } 24 | macro replace_expr($_t:tt $sub:expr) { 25 | $sub 26 | } 27 | 28 | macro_rules! count_tts_ { 29 | () => { 0 }; 30 | ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 }; 31 | ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 }; 32 | } 33 | macro count_tts { 34 | () => { 0 }, 35 | ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 }, 36 | ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 }, 37 | } 38 | 39 | fn main() {} 40 | ``` 41 | 42 | 它们看起来非常相似,只是有一些不同之处,而且 `macro` 有两种不同的形式。 43 | 44 | 让我们先看 `count_tts` 宏,因为它看起来更像我们习惯看到的样子。虽然它看起来与 45 | `macro_rules` 的版本几乎相同,但有两个不同之处: 46 | 1. 它使用了 `macro` 关键字 47 | 2. 规则分隔符是 `,` 而不是 `;` 48 | 49 | 不过,`macro` 还有另一种形式,这是只有一条规则的宏的简写。通过 50 | `replace_expr`,我们看到,可以用一种更类似于普通函数的方式来编写定义: 51 | 1. 直接在宏名字后面编写 matcher 52 | 2. 然后去掉一对大括号和 `=>`,再写 transcriber 53 | 54 | 调用 `macro` 所定义的宏,和函数式宏的语法相同,名称后跟 `!`,再后跟宏输入标记树。 55 | 56 | ## `macro` 是规范的条目 57 | 58 | `macro_rules` 宏是按文本限定范围的,并且如果将它视为条目,需要 `#[macro_export]` 59 | (而且还可能需要重导出),但 `macro` 与此不同,因为 `macro` 宏的行为与规范的条目一样。 60 | 61 | 因此,你可以使用诸如 `pub`、`pub(crate)`、`pub(in path)` 之类的可见性分类符来适当地限定它们。[^proper-item] 62 | 63 | [^proper-item]: 译者注:这也意味着,`macro` 宏的导入导出规则符合常规条目。 64 | 65 | ## 卫生性 66 | 67 | 到目前为止,卫生性是这两个声明宏系统之间最大的区别。 68 | 69 | 与具有混合式卫生性 ([mixed site hygiene](./minutiae/hygiene.md)) 的 `macro_rules` 70 | 不同,`macro` 具有定义处卫生性 ([definition site hygiene](../proc-macros/hygiene.md)),这意味着 71 | `macro` 不会将标识符泄漏到其调用之外。 72 | 73 | 这样,下面的代码可以使用 `macro_rules` 宏进行编译,但无法使用 `macro` 定义: 74 | 75 | ```rust,editable 76 | #![feature(decl_macro)] 77 | // 试着注释下面第一行,然后取消注释下面第二行,看看会发生什么 78 | 79 | macro_rules! foo { 80 | // macro foo { 81 | ($name: ident) => { 82 | pub struct $name; 83 | 84 | impl $name { 85 | pub fn new() -> $name { 86 | $name 87 | } 88 | } 89 | } 90 | } 91 | 92 | foo!(Foo); 93 | 94 | fn main() { 95 | // this fails with a `macro`, but succeeds with a `macro_rules` 96 | let foo = Foo::new(); 97 | } 98 | ``` 99 | 100 | 未来可能会有计划允许标识符卫生性逃逸 (hygiene bending)。 101 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae.md: -------------------------------------------------------------------------------- 1 | # 细节问题 2 | 3 | 本节将介绍 `macro_rules!` 宏系统的一些细枝末节。你至少应该 *记住* 有这些东西存在。 4 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/debugging.md: -------------------------------------------------------------------------------- 1 | # 调试 2 | 3 | > 注意:这是一个专门为声明宏量身定做的调试工具清单,调试宏的其他方法可以在语法扩展的 4 | > [调试][syntax-debugging] 章节中找到。 5 | 6 | ## `trace_macros!` 7 | 8 | 最有用的是 [`trace_macros!`],在每次声明宏展开前,它指示编译器记录下声明宏的调用信息。 9 | 10 | 例如: 11 | 12 | ```rust,ignore 13 | # // 注意:这需要 nightly Rust 14 | #![feature(trace_macros)] 15 | 16 | macro_rules! each_tt { 17 | () => {}; 18 | ($_tt:tt $($rest:tt)*) => {each_tt!($($rest)*);}; 19 | } 20 | 21 | each_tt!(foo bar baz quux); 22 | trace_macros!(true); 23 | each_tt!(spim wak plee whum); 24 | trace_macros!(false); 25 | each_tt!(trom qlip winp xod); 26 | # 27 | # fn main() {} 28 | ``` 29 | 30 | 输出为: 31 | 32 | ```text 33 | note: trace_macro 34 | --> src/main.rs:11:1 35 | | 36 | 11 | each_tt!(spim wak plee whum); 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | | 39 | = note: expanding `each_tt! { spim wak plee whum }` 40 | = note: to `each_tt ! (wak plee whum) ;` 41 | = note: expanding `each_tt! { wak plee whum }` 42 | = note: to `each_tt ! (plee whum) ;` 43 | = note: expanding `each_tt! { plee whum }` 44 | = note: to `each_tt ! (whum) ;` 45 | = note: expanding `each_tt! { whum }` 46 | = note: to `each_tt ! () ;` 47 | = note: expanding `each_tt! { }` 48 | = note: to `` 49 | ``` 50 | 51 | 它在调试递归很深的宏时尤其有用。 52 | 53 | 此外,你可以在命令行里,给编译指令附加 `-Z trace-macros` 来打印追踪的宏。 54 | 55 | `trace_macros!(false);` 之后的宏不会被这个附加指令追踪到,所以这里会追踪前两个宏。 56 | 57 | 参考命令:`cargo rustc --bin binary_name -- -Z trace-macros` 58 | 59 | ## `log_syntax!` 60 | 61 | 另一有用的宏是 [`log_syntax!`]。它将使得编译器输出所有经过编译器处理的标记。 62 | 63 | 比如让编译器“唱首歌”: 64 | 65 | ```rust 66 | # // 注意:这需要 nightly Rust 67 | #![feature(log_syntax)] 68 | 69 | macro_rules! sing { 70 | () => {}; 71 | ($tt:tt $($rest:tt)*) => {log_syntax!($tt); sing!($($rest)*);}; 72 | } 73 | 74 | sing! { 75 | ^ < @ < . @ * 76 | '\x08' '{' '"' _ # ' ' 77 | - @ '$' && / _ % 78 | ! ( '\t' @ | = > 79 | ; '\x08' '\'' + '$' ? '\x7f' 80 | , # '"' ~ | ) '\x07' 81 | } 82 | # 83 | # fn main() {} 84 | ``` 85 | 86 | 比起 `trace_macros!` 来说,它能够做一些更有针对性的调试。 87 | 88 | ## `macro_railroad` lib 89 | 90 | 另一个很棒的工具是 [lukaslueg] 编写的 [`macro_railroad`] lib。 91 | 92 | 它能可视化地生成 Rust `macro_rules!` 宏的语法图 (syntax diagrams)。 93 | 94 | 它还有一个[浏览器插件][railroad-ext],和一个可动态可视化声明宏的[静态网页][railroad-demo]。 95 | 96 | [syntax-debugging]: ../../syntax-extensions/debugging.md 97 | [`trace_macros!`]: https://doc.rust-lang.org/std/macro.trace_macros.html 98 | [`log_syntax!`]: https://doc.rust-lang.org/std/macro.log_syntax.html 99 | [lukaslueg]: https://github.com/lukaslueg 100 | [`macro_railroad`]: https://github.com/lukaslueg/macro_railroad 101 | [railroad-ext]: https://github.com/lukaslueg/macro_railroad_ext 102 | [railroad-demo]: https://lukaslueg.github.io/macro_railroad_wasm_demo 103 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/fragment-specifiers.md: -------------------------------------------------------------------------------- 1 | # 片段分类符 2 | 3 | 正如在 [思路](../macros-methodical.md) 一章看到的,截至 1.60 版本, Rust 已有 4 | 14 个片段分类符 (Fragment Specifiers,以下简称分类符)[^metavariables]。 5 | 6 | 这一节会更深入地探讨他们之中的细节,每次都会展示几个匹配的例子。 7 | 8 | > 注意:除了 `ident`、`lifetime` 和 `tt` 分类符之外,其余的分类符在匹配后生成的 9 | > AST 是不清楚的 (opaque),这使得在之后的宏调用时不可能检查 (inspect) 捕获的结果。[^opaque] 10 | 11 | * [`block`](#block) 12 | * [`expr`](#expr) 13 | * [`ident`](#ident) 14 | * [`item`](#item) 15 | * [`lifetime`](#lifetime) 16 | * [`literal`](#literal) 17 | * [`meta`](#meta) 18 | * [`pat`](#pat) 19 | * [`pat_param`](#pat_param) 20 | * [`path`](#path) 21 | * [`stmt`](#stmt) 22 | * [`tt`](#tt) 23 | * [`ty`](#ty) 24 | * [`vis`](#vis) 25 | 26 | [^metavariables]: 最新内容可参考 *Reference* 的 [Metavariables](https://doc.rust-lang.org/nightly/reference/macros-by-example.html#metavariables) 一节。 27 | [^opaque]: 推荐通过 rust quiz [#9](https://dtolnay.github.io/rust-quiz/9) 来理解这句话。 28 | 29 | ## `block` 30 | 31 | `block` 分类符只匹配 32 | [block 表达式](https://doc.rust-lang.org/reference/expressions/block-expr.html)。 33 | 34 | 块 (block) 由 `{` 开始,接着是一些语句,最后是可选的表达式,然后以 `}` 结束。 35 | 块的类型要么是最后的值表达式类型,要么是 `()` 类型。 36 | 37 | ```rust,editable 38 | macro_rules! blocks { 39 | ($($block:block)*) => (); 40 | } 41 | 42 | blocks! { 43 | {} 44 | { 45 | let zig; 46 | } 47 | { 2 } 48 | } 49 | fn main() {} 50 | ``` 51 | 52 | ## `expr` 53 | 54 | `expr` 分类符用于匹配任何形式的表达式 55 | ([expression](https://doc.rust-lang.org/reference/expressions.html))。 56 | 57 | (如果把 Rust 视为面向表达式的语言,那么它有很多种表达式。) 58 | 59 | ```rust,editable 60 | macro_rules! expressions { 61 | ($($expr:expr)*) => (); 62 | } 63 | 64 | expressions! { 65 | "literal" 66 | funcall() 67 | future.await 68 | break 'foo bar 69 | } 70 | fn main() {} 71 | ``` 72 | 73 | ## `ident` 74 | 75 | `ident` 分类符用于匹配任何形式的标识符 76 | ([identifier](https://doc.rust-lang.org/reference/identifiers.html)) 或者关键字。 77 | 。 78 | 79 | ```rust,editable 80 | macro_rules! idents { 81 | ($($ident:ident)*) => (); 82 | } 83 | 84 | idents! { 85 | // _ <- `_` 不是标识符,而是一种模式 86 | foo 87 | async 88 | O_________O 89 | _____O_____ 90 | } 91 | fn main() {} 92 | ``` 93 | 94 | ## `item` 95 | 96 | `item` 分类符只匹配 Rust 的 [item](https://doc.rust-lang.org/reference/items.html) 97 | 的 **定义** (definitions) , 98 | 不会匹配指向 item 的标识符 (identifiers)。例子: 99 | 100 | ```rust,editable 101 | macro_rules! items { 102 | ($($item:item)*) => (); 103 | } 104 | 105 | items! { 106 | struct Foo; 107 | enum Bar { 108 | Baz 109 | } 110 | impl Foo {} 111 | /*...*/ 112 | } 113 | fn main() {} 114 | ``` 115 | 116 | [`item`](https://doc.rust-lang.org/reference/items.html) 117 | 是在编译时完全确定的,通常在程序执行期间保持固定,并且可以驻留在只读存储器中。具体指: 118 | 119 | * [modules](https://doc.rust-lang.org/reference/items/modules.html) 120 | * [`extern crate` declarations](https://doc.rust-lang.org/reference/items/extern-crates.html) 121 | * [`use` declarations](https://doc.rust-lang.org/reference/items/use-declarations.html) 122 | * [function definitions](https://doc.rust-lang.org/reference/items/functions.html) 123 | * [type definitions](https://doc.rust-lang.org/reference/items/type-aliases.html) 124 | * [struct definitions](https://doc.rust-lang.org/reference/items/structs.html) 125 | * [enumeration definitions](https://doc.rust-lang.org/reference/items/enumerations.html) 126 | * [union definitions](https://doc.rust-lang.org/reference/items/unions.html) 127 | * [constant items](https://doc.rust-lang.org/reference/items/constant-items.html) 128 | * [static items](https://doc.rust-lang.org/reference/items/static-items.html) 129 | * [trait definitions](https://doc.rust-lang.org/reference/items/traits.html) 130 | * [implementations](https://doc.rust-lang.org/reference/items/implementations.html) 131 | * [`extern` blocks](https://doc.rust-lang.org/reference/items/external-blocks.html) 132 | 133 | ## `lifetime` 134 | 135 | `lifetime` 分类符用于匹配生命周期注解或者标签 136 | ([lifetime or label](https://doc.rust-lang.org/reference/tokens.html#lifetimes-and-loop-labels))。 137 | 它与 [`ident`](#ident) 很像,但是 `lifetime` 会匹配到前缀 `''` 。 138 | 139 | ```rust,editable 140 | macro_rules! lifetimes { 141 | ($($lifetime:lifetime)*) => (); 142 | } 143 | 144 | lifetimes! { 145 | 'static 146 | 'shiv 147 | '_ 148 | } 149 | fn main() {} 150 | ``` 151 | 152 | ## `literal` 153 | 154 | `literal` 分类符用于匹配字面表达式 155 | ([literal expression](https://doc.rust-lang.org/reference/expressions/literal-expr.html))。 156 | 157 | ```rust,editable 158 | macro_rules! literals { 159 | ($($literal:literal)*) => (); 160 | } 161 | 162 | literals! { 163 | -1 164 | "hello world" 165 | 2.3 166 | b'b' 167 | true 168 | } 169 | fn main() {} 170 | ``` 171 | 172 | ## `meta` 173 | 174 | `meta` 分类符用于匹配属性 ([attribute](https://doc.rust-lang.org/reference/attributes.html)), 175 | 准确地说是属性里面的内容。通常你会在 `#[$meta:meta]` 或 `#![$meta:meta]` 模式匹配中 176 | 看到这个分类符。 177 | 178 | ```rust,editable 179 | macro_rules! metas { 180 | ($($meta:meta)*) => (); 181 | } 182 | 183 | metas! { 184 | ASimplePath 185 | super::man 186 | path = "home" 187 | foo(bar) 188 | } 189 | fn main() {} 190 | ``` 191 | 192 | > 针对文档注释简单说一句: 193 | > 文档注释其实是具有 `#[doc="…"]` 形式的属性,`...` 实际上就是注释字符串, 194 | > 这意味着你可以在在宏里面操作文档注释! 195 | 196 | ## `pat` 197 | 198 | `pat` 分类符用于匹配任何形式的模式 199 | ([pattern](https://doc.rust-lang.org/reference/patterns.html)),包括 2021 edition 200 | 开始的 [or-patterns](https://doc.rust-lang.org/reference/patterns.html#or-patterns)。 201 | 202 | ```rust,editable,edition2021 203 | macro_rules! patterns { 204 | ($($pat:pat)*) => (); 205 | } 206 | 207 | patterns! { 208 | "literal" 209 | _ 210 | 0..5 211 | ref mut PatternsAreNice 212 | 0 | 1 | 2 | 3 213 | } 214 | fn main() {} 215 | ``` 216 | 217 | ## `pat_param` 218 | 219 | 从 2021 edition 起, or-patterns 模式开始应用,这让 `pat` 分类符不再允许跟随 `|`。 220 | 221 | 为了避免这个问题或者说恢复旧的 `pat` 分类符行为,你可以使用 `pat_param` 片段,它允许 222 | `|` 跟在它后面,因为 `pat_param` 不允许 top level 或 or-patterns。 223 | 224 | ```rust,editable,edition2021 225 | macro_rules! patterns { 226 | (pat: $pat:pat) => { 227 | println!("pat: {}", stringify!($pat)); 228 | }; 229 | (pat_param: $($pat:pat_param)|+) => { 230 | $( println!("pat_param: {}", stringify!($pat)); )+ 231 | }; 232 | } 233 | fn main() { 234 | patterns! { 235 | pat: 0 | 1 | 2 | 3 236 | } 237 | patterns! { 238 | pat_param: 0 | 1 | 2 | 3 239 | } 240 | } 241 | ``` 242 | 243 | ```rust,editable,edition2021 244 | macro_rules! patterns { 245 | ($( $( $pat:pat_param )|+ )*) => (); 246 | } 247 | 248 | patterns! { 249 | "literal" 250 | _ 251 | 0..5 252 | ref mut PatternsAreNice 253 | 0 | 1 | 2 | 3 254 | } 255 | fn main() {} 256 | ``` 257 | 258 | ## `path` 259 | 260 | `path` 分类符用于匹配类型中的路径 261 | ([TypePath](https://doc.rust-lang.org/reference/paths.html#paths-in-types))。 262 | 263 | 这包括函数式的 trait 形式。 264 | 265 | ```rust,editable 266 | macro_rules! paths { 267 | ($($path:path)*) => (); 268 | } 269 | 270 | paths! { 271 | ASimplePath 272 | ::A::B::C::D 273 | G::::C 274 | FnMut(u32) -> () 275 | } 276 | fn main() {} 277 | ``` 278 | 279 | ## `stmt` 280 | 281 | `stmt` 分类符只匹配的 语句 ([statement](https://doc.rust-lang.org/reference/statements.html))。 282 | 除非 item 语句要求结尾有分号,否则 **不会** 匹配语句最后的分号。 283 | 284 | 什么叫 item 语句要求结尾有分号呢?单元结构体 (Unit-Struct) 就是一个简单的例子, 285 | 因为它定义中必须带上结尾的分号。 286 | 287 | 赶紧用例子展示上面说的是啥意思吧。下面的宏只给出它所捕获的内容,因为有几行不能通过编译。 288 | 289 | ```rust,editable 290 | macro_rules! statements { 291 | ($($stmt:stmt)*) => ($($stmt)*); 292 | } 293 | 294 | fn main() { 295 | statements! { 296 | struct Foo; 297 | fn foo() {} 298 | let zig = 3 299 | let zig = 3; 300 | 3 301 | 3; 302 | if true {} else {} 303 | {} 304 | } 305 | } 306 | ``` 307 | 308 | 你可以根据报错内容试着删除不能编译的代码,结合 [`stmt`](#stmt) 小节开头的文字再琢磨琢磨。 309 | 你如果正浏览使用 mdbook 渲染的页面,那么可以直接运行和修改这段代码。 310 | 311 | 虽然源代码编译失败,但是我们可以展开宏[^debugging], 312 | 使用 [playground](https://play.rust-lang.org/) 的 313 | `Expand macros` 工具 (tool);或者把代码复制到本地,在 nightly Rust 版本中使用 314 | `cargo rustc -- -Zunstable-options --pretty=expanded` 命令得到宏展开结果: 315 | 316 | ```rust,ignore 317 | # warning: unnecessary trailing semicolon 318 | # --> src/main.rs:10:20 319 | # | 320 | # 10 | let zig = 3; 321 | # | ^ help: remove this semicolon 322 | # | 323 | # = note: `#[warn(redundant_semicolons)]` on by default 324 | # 325 | # warning: unnecessary trailing semicolon 326 | # --> src/main.rs:12:10 327 | # | 328 | # 12 | 3; 329 | # | ^ help: remove this semicolon 330 | # 331 | # #![feature(prelude_import)] 332 | # #[prelude_import] 333 | # use std::prelude::rust_2018::*; 334 | # #[macro_use] 335 | # extern crate std; 336 | # macro_rules! statements { ($ ($ stmt : stmt) *) => ($ ($ stmt) *) ; } 337 | 338 | fn main() { 339 | struct Foo; 340 | fn foo() { } 341 | let zig = 3; 342 | let zig = 3; 343 | ; 344 | 3; 345 | 3; 346 | ; 347 | if true { } else { } 348 | { } 349 | } 350 | ``` 351 | 352 | 由此我们知道: 353 | 354 | 1. 虽然 `stmt` 分类符没有捕获语句末尾的分号,但它依然在所需的时候返回了 (emit) 355 | 语句。原因很简单,分号本身就是有效的语句。所以我们实际输入 10 个语句调用了宏,而不是 8 356 | 个!这在把多个反复捕获放入一个反复展开时很重要,因为此时反复的次数必须相同。 357 | 358 | 2. 在这里你应该注意到:`struct Foo;` 被匹配到了。否则我们会看到像其他情况一样有一个额外 `;` 359 | 语句。由前所述,这能想通:item 语句需要分号,所以这个分号能被匹配到。 360 | 361 | 3. 仅由块表达式或控制流表达式组成的表达式结尾没有分号, 362 | 其余的表达式捕获后产生的表达式会尾随一个分号(在这个例子中,正是这里出错)。 363 | 364 | 这里提到的细节能在 Reference 的 [statement](https://doc.rust-lang.org/reference/statements.html) 365 | 一节中找到。但个细节通常这并不重要,除了要注意反复次数,通常没什么问题。 366 | 367 | [^debugging]: 可阅读 [调试](./debugging.md) 一章 368 | 369 | ## `tt` 370 | 371 | `tt` 分类符用于匹配标记树 (TokenTree)。 372 | 如果你是新手,对标记树不了解,那么需要回顾本书 373 | [标记树](../syntax/source-analysys.html#标记树-token-trees) 374 | 一节。`tt` 分类符是最有作用的分类符之一,因为它能匹配几乎所有东西, 375 | 而且能够让你在使用宏之后检查 (inspect) 匹配的内容。 376 | 377 | 这让你可以编写非常强大的宏技巧,比如 378 | [tt-muncher](../patterns/tt-muncher.md) 和 379 | [push-down-accumulator](../patterns/push-down-acc.md)。 380 | 381 | ## `ty` 382 | 383 | `ty` 分类符用于匹配任何形式的类型表达式 ([type expression](https://doc.rust-lang.org/reference/types.html#type-expressions))。 384 | 385 | 类型表达式是在 Rust 中指代类型的语法。 386 | 387 | ```rust,editable 388 | macro_rules! types { 389 | ($($type:ty)*) => (); 390 | } 391 | 392 | types! { 393 | foo::bar 394 | bool 395 | [u8] 396 | impl IntoIterator 397 | } 398 | fn main() {} 399 | ``` 400 | 401 | ## `vis` 402 | 403 | `vis` 分类符会匹配 **可能为空** 可见性修饰符 ([Visibility qualifier](https://doc.rust-lang.org/reference/visibility-and-privacy.html))。 404 | 405 | ```rust,editable 406 | macro_rules! visibilities { 407 | // ∨~~注意这个逗号,`vis` 分类符自身不会匹配到逗号 408 | ($($vis:vis,)*) => (); 409 | } 410 | 411 | visibilities! { 412 | , // 没有 vis 也行,因为 $vis 隐式包含 `?` 的情况 413 | pub, 414 | pub(crate), 415 | pub(in super), 416 | pub(in some_path), 417 | } 418 | fn main() {} 419 | ``` 420 | 421 | `vis` 实际上只支持例子里的几种方式,因为这里的 visibility 422 | 指的是可见性,与私有性相对。而涉及这方面的内容只有与 `pub` 423 | 的关键字。所以,`vis` 在关心匹配输入的内容是公有还是私有时有用。 424 | 425 | 此外,如果匹配时,其后没有标记流,整个宏会匹配失败: 426 | 427 | ```rust,editable 428 | macro_rules! non_optional_vis { 429 | ($vis:vis) => (); 430 | } 431 | non_optional_vis!(); 432 | // ^^^^^^^^^^^^^^^^ error: missing tokens in macro arguments 433 | fn main() {} 434 | ``` 435 | 436 | 重点在于“可能为空”。你可能想到这是隐藏了 `?` 437 | 重复操作符的分类符,这样你就不用直接在反复匹配时使用 438 | `?` —— 其实你不能将它和 `?` 一起在重复模式匹配中使用。 439 | 440 | 可以匹配 `$vis:vis $ident:ident`,但不能匹配 `$(pub)? $ident:ident`,因为 `pub` 441 | 表明一个有效的标识符,所以后者是模糊不清的。 442 | 443 | ```rust,editable 444 | macro_rules! vis_ident { 445 | ($vis:vis $ident:ident) => (); 446 | } 447 | vis_ident!(pub foo); // this works fine 448 | 449 | macro_rules! pub_ident { 450 | ($(pub)? $ident:ident) => (); 451 | } 452 | pub_ident!(pub foo); 453 | // ^^^ error: local ambiguity when calling macro `pub_ident`: multiple parsing options: built-in NTs ident ('ident') or 1 other option. 454 | fn main() {} 455 | ``` 456 | 457 | 而且,搭配 `tt` 分类符和递归展开去匹配空标记也会导致有趣而奇怪的事情。 458 | 459 | 当 `pub` 匹配了空标记,元变量依然算一次被捕获,又因为它不是 `tt`、`ident` 或 460 | `lifetime`,所以再次展开时是不清楚的。 461 | 462 | 这意味着如果这种捕获的结果传递给另一个将它视为 `tt` 的宏调用,你最终得到一棵空的标记树。 463 | 464 | ```rust,editable 465 | macro_rules! it_is_opaque { 466 | (()) => { "()" }; 467 | (($tt:tt)) => { concat!("$tt is ", stringify!($tt)) }; 468 | ($vis:vis ,) => { it_is_opaque!( ($vis) ); } 469 | } 470 | fn main() { 471 | // this prints "$tt is ", as the recursive calls hits the second branch with 472 | // an empty tt, opposed to matching with the first branch! 473 | println!("{}", it_is_opaque!(,)); 474 | } 475 | ``` 476 | 477 | -------------------------------------------------------------------------------- /src/decl-macros/minutiae/hygiene.md: -------------------------------------------------------------------------------- 1 | # 卫生性 2 | 3 | > 译者注:卫生性 (hygiene) 描述的是 **标识符** 4 | > 在宏处理和展开过程中是“宏定义处的标识符不与外部定义的标识符交互”、“不被外部同名标识符污染的”。见 5 | > [卫生性和 Span](https://zjp-cn.github.io/tlborm/proc-macros/hygiene.html)。 6 | 7 | ## 宏是部分卫生的 8 | 9 | Rust 里的声明宏是 **部分** 卫生的 (partially hygienic 或者称作 mixed hygiene)。 10 | 11 | 具体来说,对于以下内容,声明宏是卫生的: 12 | 13 | 1. 局部变量 (local variables) 14 | 2. [labels](https://doc.rust-lang.org/reference/expressions/loop-expr.html#loop-labels) 15 | 3. [`$crate`](#crate-元变量) 16 | 17 | **除此之外,声明宏都不是卫生的。**[^unhygientic] 18 | 19 | [^unhygientic]: 推荐尝试 Rust Quiz [#24](https://dtolnay.github.io/rust-quiz/24),并阅读 [“Truly Hygienic” Let Statements in Rust](https://sabrinajewson.org/blog/truly-hygienic-let)。 20 | 21 | 之所以能做到“卫生”,是因为每个标识符都被赋予了一个看不见的“句法上下文” 22 | (syntax context)。在比较两个标识符时,只有在标识符的原文名称和句法上下文都 23 | **完全一样** 的情况下,两个标识符才能被视作等同。 24 | 25 | 为阐释这一点,考虑下述代码: 26 | 27 |
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 { 19 | Some(s.len()) 20 | } 21 | 22 | fn show_tail(s: &str) -> Option { 23 | println!("tail: {:?}", s); 24 | None 25 | } 26 | 27 | fn main() { 28 | assert_eq!( 29 | call_a_or_b_on_tail!( 30 | (a: compute_len, b: show_tail), 31 | the recursive part that skips over all these 32 | tokens doesn't much care whether we will call a 33 | or call b: only the terminal rules care. 34 | ), 35 | None 36 | ); 37 | assert_eq!( 38 | call_a_or_b_on_tail!( 39 | (a: compute_len, b: show_tail), 40 | and now, to justify the existence of two paths 41 | we will also call a: its input should somehow 42 | be self-referential, so let's make it return 43 | some eighty-six! 44 | ), 45 | Some(92) 46 | ); 47 | } 48 | ``` 49 | 50 | 在十分复杂的递归宏中,可能需要非常多的参数, 51 | 才足以在每层调用之间传递必要的标识符与表达式。 52 | 然而,根据实现上的差异,可能存在许多这样的中间层, 53 | 它们转发了 (forward) 这些参数,但并没有用到。 54 | 55 | 因此,将所有这些参数捆绑 (bundle) 在一起,通过分组将其放进单独一棵标记树 [`tt`] 里, 56 | 可以省事许多。这样一来,那些用不到这些参数的递归层可以直接捕获并替换这棵标记树, 57 | 而不需要把整组参数完完全全准准确确地捕获替换掉。 58 | 59 | 上面的例子把表达式 `$a` 和 `$b` 捆绑起来, 60 | 然后作为一棵 [`tt`] 交由递归规则处理。 61 | 随后,终结规则 (terminal rules) 将这组标记解构 (destructure) , 62 | 并访问其中的表达式。 63 | 64 | [`tt`]: ../minutiae/fragment-specifiers.html#tt 65 | -------------------------------------------------------------------------------- /src/decl-macros/patterns/tt-muncher.md: -------------------------------------------------------------------------------- 1 | # 增量式 `TT` “撕咬机” 2 | 3 | > 译者注:原文标题为 *incremental `TT` muncher* 。 4 | 5 | ```rust 6 | macro_rules! mixed_rules { 7 | () => {}; 8 | (trace $name:ident; $($tail:tt)*) => { 9 | { 10 | println!(concat!(stringify!($name), " = {:?}"), $name); 11 | mixed_rules!($($tail)*); 12 | } 13 | }; 14 | (trace $name:ident = $init:expr; $($tail:tt)*) => { 15 | { 16 | let $name = $init; 17 | println!(concat!(stringify!($name), " = {:?}"), $name); 18 | mixed_rules!($($tail)*); 19 | } 20 | }; 21 | } 22 | # 23 | # fn main() { 24 | # let a = 42; 25 | # let b = "Ho-dee-oh-di-oh-di-oh!"; 26 | # let c = (false, 2, 'c'); 27 | # mixed_rules!( 28 | # trace a; 29 | # trace b; 30 | # trace c; 31 | # trace b = "They took her where they put the crazies."; 32 | # trace b; 33 | # ); 34 | # } 35 | ``` 36 | 37 | 此模式可能是 **最强大** 的宏解析技巧。通过使用它,一些极其复杂的语法都能得到解析。 38 | 39 | 40 | “标记树撕咬机” (`TT` muncher) 是一种递归宏,其工作机制有赖于对输入的顺次、逐步处理 41 | (incrementally processing) 。处理过程的每一步中,它都将匹配并移除(“撕咬”掉)输入头部 42 | (start) 的一列标记 (tokens),得到一些中间结果,然后再递归地处理输入剩下的尾部。 43 | 44 | 名称中含有“标记树”,是因为输入中尚未被处理的部分总是被捕获在 `$($tail:tt)*` 45 | 的形式中。之所以如此,是因为只有通过使用反复匹配 [`tt`] 才能做到 **无损地** 46 | (losslessly) 捕获住提供给宏的输入部分。 47 | 48 | 标记树撕咬机仅有的限制,也是整个宏系统的局限: 49 | 50 | * 你只能匹配 `macro_rules!` 捕获到的字面值和语法结构。 51 | * 你无法匹配不成对的标记组 (unbalanced group) 。 52 | 53 | 然而,需要把宏递归的局限性纳入考量。`macro_rules!` 没有做任何形式的尾递归消除或优化。 54 | 55 | 在写标记树撕咬机时,建议多花些功夫,尽可能地限制递归调用的次数。 56 | 57 | 以下两种做法帮助你做到限制宏递归: 58 | 1. 对于输入的变化,增加额外的匹配规则(而不是采用中间层并使用递归)[^example]; 59 | 2. 对输入句法施加限制,以便于记录追踪标准式的反复匹配。 60 | 61 | [^example]: 例子见 [计数-递归](../building-blocks/counting.html#递归) 62 | 63 | [`tt`]:../minutiae/fragment-specifiers.md#tt 64 | 65 | 66 | 67 | # 性能建议 68 | 69 | > 译者注:要点是 70 | > 1. 可以一次处理很多标记来减少递归次数(比如运用反复匹配) 71 | > 2. 可以编写规则简单的宏,然后多次调用 72 | > 3. 把容易匹配到的规则放到前面,以减少匹配次数(因为规则顺序决定了匹配顺序) 73 | 74 | TT 撕咬机天生就是二次复杂度的。考虑一个 TT 撕咬机 75 | 规则,它消耗一个标记树,然后递归地在其余输入上调用自身。如果向其传递 100 个标记树: 76 | 77 | - 初始调用将匹配所有的 100 个标记树。 78 | - 第 1 个递归调用将匹配 99 个标记树。 79 | - 下一次递归调用将匹配 98 个标记树。 80 | - 依此类推,直到匹配最后 1 个标记树。 81 | 82 | 这是一个典型的二次复杂度模式,过长的输入会导致宏展开延长编译时间。 83 | 84 | 因此,尽量避免过多地使用 TT 撕咬机,特别是在输入较长的情况下。 85 | 86 | `recursion_limit` 属性的缺省值 (目前是 128 ) 87 | 是一个良好的健全性检查;如果你必须超过它,那么可能会遇到麻烦。 88 | 89 | 建议是,你可以选择编写一个: 90 | 1. 一次调用就能处理多件事情的 TT 撕咬机 91 | 2. 或者多次调用来处理一件事情的更简单的宏(这种宏从性能角度看,是更推荐的做法) 92 | 93 | 例如,别这样写: 94 | 95 | ```rust 96 | # macro_rules! f { ($($tt:tt)*) => {} } 97 | f! { 98 | fn f_u8(x: u32) -> u8; 99 | fn f_u16(x: u32) -> u16; 100 | fn f_u32(x: u32) -> u32; 101 | fn f_u64(x: u64) -> u64; 102 | fn f_u128(x: u128) -> u128; 103 | } 104 | ``` 105 | 106 | 应该这样写: 107 | 108 | ```rust 109 | # macro_rules! f { ($($tt:tt)*) => {} } 110 | f! { fn f_u8(x: u32) -> u8; } 111 | f! { fn f_u16(x: u32) -> u16; } 112 | f! { fn f_u32(x: u32) -> u32; } 113 | f! { fn f_u64(x: u64) -> u64; } 114 | f! { fn f_u128(x: u128) -> u128; } 115 | ``` 116 | 117 | 宏的输入越长,第二种编写方式就越有可能缩短编译时间。 118 | 119 | 此外,如果 TT 撕咬机有许多规则,请 **尽可能把最频繁匹配的规则放到前面** 120 | 。这避免了不必要的匹配失败。(事实上,这对任何类型的声明性宏都是很好的建议,而不仅仅是 121 | TT 撕咬机。) 122 | 123 | 最后,优先使用正常的反复匹配(`*` 或 `+`)来编写宏,这比 TT 撕咬机更好。如果每次调用 124 | TT 撕咬机时,一次只处理一个标记,则最有可能出现这种情况。 125 | 126 | 在更复杂的情况下,可以参考 `quote!` 使用的一种高级技术,它可以避免二次复杂度,而且不会达到递归上限,但代价是一些复杂的概念。详情请参考[此处][quote]。 127 | 128 | [quote]: https://github.com/dtolnay/quote/blob/31c3be473d0457e29c4f47ab9cff73498ac804a7/src/lib.rs#L664-L746 129 | 130 | -------------------------------------------------------------------------------- /src/introduction.md: -------------------------------------------------------------------------------- 1 | {{#include ../README.md}} -------------------------------------------------------------------------------- /src/proc-macros.md: -------------------------------------------------------------------------------- 1 | # 过程宏 2 | 3 | 本章将介绍 Rust 的第二种语法扩展类型:过程宏。 4 | 5 | 与 [声明宏](./decl-macros.md) 一样,这一部分也分为 [思路](./proc-macros/methodical.md) 6 | 和实战 (WIP) 的两个子章节,前者是更正式的介绍,后者是更注重实践的子章。 7 | 8 | 文中介绍的许多基本信息都来自于 [Rust Reference],因为目前关于过程宏的大部分知识都在那。 9 | 10 | > 译者注:在翻译(或者说知道)这部分内容之前,我自己也总结了一些学习过程宏的经验,你可以[在这][note]看到。 11 | 12 | [Rust Reference]: https://doc.rust-lang.org/nightly/reference/introduction.html 13 | [note]: https://zjp-cn.github.io/rust-note/proc-macro-note.html 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/proc-macros/hygiene.md: -------------------------------------------------------------------------------- 1 | # 卫生性和 `Span` 2 | 3 | 本章讨论过程宏的[卫生性][hygiene]以及对其进行编码的类型 [`Span`]。 4 | 5 | [`TokenStream`] 中的每个标记都关联了一个 `Span`,它其中包含一些附加信息。 6 | 7 | 正如其文档所述,`Span` 表示“一个源代码区域,以及宏展开的信息”。 8 | 9 | `Span` 指向原始源代码的一个区域(这对于在正确的位置显示诊断信息很重要),并保持该位置的卫生性。 10 | 11 | 卫生性主要与标识符有关,因为它允许或禁止表示符对调用外部定义的事物进行引用或者被引用。 12 | 13 | 卫生性有 3 种(这可以从 `Span` 类型的构造函数看到): 14 | 15 | * 定义处卫生性 ([`definition site`]) (***unstable***): 表示宏定义处的 `Span`。带着这种 `Span` 16 | 的标识符不能引用外部定义的内容(即这种标识符无法使用宏定义之外的内容),或者不能被外部调用的东西引用(即宏定义之外的东西无法使用这种标识符)。这就是所谓的“卫生性”。 17 | * 混合式卫生性 ([`mixed site`]):表示宏定义处或者调用处的 `Span`,具体取决于标识符的类型。声明宏使用这种卫生性,见此[此章](../decl-macros/minutiae/hygiene.md)。 18 | * 调用处卫生性 ([`call site`]):表示调用处的 19 | `Span`。此时,标识符表现得就像是直接在调用处编写的一样,也就是说,它们可以自由地使用调用之外定义的内容,也可以从外部引用它们。这就是所谓的“不卫生” 20 | (unhygienic)。 21 | 22 | [hygiene]: ../syntax-extensions/hygiene.md 23 | [`Span`]: https://doc.rust-lang.org/proc_macro/struct.Span.html 24 | [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html 25 | [`definition site`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.def_site 26 | [`mixed site`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.mixed_site 27 | [`call site`]: https://doc.rust-lang.org/proc_macro/struct.Span.html#method.call_site 28 | -------------------------------------------------------------------------------- /src/proc-macros/methodical.md: -------------------------------------------------------------------------------- 1 | # 思路介绍 2 | 3 | 本章将从整体角度解释和介绍过程宏。 4 | 5 | 与[声明宏]不同,过程宏采用 Rust 函数的形式,接受一个(或两个)标记流并输出一个标记流。 6 | 7 | [声明宏]: ../decl-macros.md 8 | 9 | 过程宏的核心只是一个从 `proc-macro` [crate type](https://doc.rust-lang.org/reference/linkage.html) 这种类型的库中所导出的公有函数,因此当编写多个过程宏时,你可以将它们全部放在一个 crate 中。 10 | 11 | > 注意:在使用 Cargo 时,定义一个 `proc-macro` crate 的方式是将 12 | > `Cargo.toml` 中的 `lib.proc-macro` 键设置为 true,就像这样 13 | > 14 | > ```toml 15 | > [lib] 16 | > proc-macro = true 17 | > ``` 18 | 19 | `proc-macro` 类型的 crate 会隐式链接到编译器提供的 [proc_macro](https://doc.rust-lang.org/proc_macro/index.html) 库, 20 | proc_macro 库包含了开发过程宏所需的所有内容,并且它公开了两个最重要的类型: 21 | 1. [`TokenStream`](https://doc.rust-lang.org/proc_macro/struct.TokenStream.html):它表示我们所熟知的标记树 22 | 2. [`Span`](https://doc.rust-lang.org/proc_macro/struct.Span.html):它表示源代码的一部分,主要用于错误信息的报告和卫生性,更多信息请阅读 23 | [卫生性和 Spans](./hygiene.md) 一章 24 | 25 | 因为过程宏是存在于 crate 中的函数,所以它们可以像 Rust 项目中的所有其他条目一样使用。 26 | 27 | 使用过程宏只需要将 proc-macro 类型的 crate 添加到项目的依赖关系图中,并将所需的过程宏引入作用域。 28 | 29 | > 注意:调用过程宏与编译器展开成声明宏是在同一阶段运行,只是过程宏是编译器编译、运行、最后替换或追加的独立的 Rust 程序。 30 | 31 | ## 过程宏的类型 32 | 33 | 过程宏实际上存在三种不同的类型,每种类型的性质都略有不同。[^kinds] 34 | 35 | * 函数式:实现 `$name!$input` 功能的宏 36 | * 属性式:实现 `#[$input]` 功能的属性 37 | * derive 式:实现 `#[derive($name)]` 功能的属性 38 | 39 | [^kinds]: 译者注:你可以参考我[总结的表](https://zjp-cn.github.io/rust-note/proc/FAQ.html#%E4%BB%80%E4%B9%88%E6%98%AF%E8%BF%87%E7%A8%8B%E5%AE%8F) 40 | 41 | ### 函数式 42 | 43 | ```rust,ignore 44 | #[proc_macro] 45 | pub fn name(input: TokenStream) -> TokenStream { 46 | TokenStream::new() 47 | } 48 | ``` 49 | 50 | ### 属性式 51 | 52 | ```rust,ignore 53 | #[proc_macro_attribute] 54 | pub fn name(attr: TokenStream, input: TokenStream) -> TokenStream { 55 | TokenStream::new() 56 | } 57 | ``` 58 | 59 | ### derive 式 60 | 61 | ```rust,ignore 62 | #[proc_macro_derive(Name)] 63 | pub fn my_derive(input: TokenStream) -> TokenStream { 64 | TokenStream::new() 65 | } 66 | ``` 67 | 68 | 如上所示,每个函数的基本结构是相同的:一个标记了一个属性的公有函数,这个属性定义了它的过程性宏类型,然后函数返回一个 `TokenStream`。 69 | 70 | 注意,返回类型必须是一个 `TokenStream`[^TokenStream]。 71 | 72 | 过程宏也会失败,它们有两种报告错误的方式: 73 | 1. panic:此时编译器会捕获到,然后把它作为来自于宏调用的错误发出 74 | 2. 调用 [`compile_error!`](https://doc.rust-lang.org/std/macro.compile_error.html) 75 | 76 | > 注意:如果过程宏内出现无限循环,编译器会长时间等待(挂起),从而造成使用过程宏的 crate 也编译挂起。 77 | 78 | [^TokenStream]: 译者注:而且这个 `TokenStream` 类型必须是 `proc_macro` 所公开的 `TokenStream`,通常使用 `quote` 库构造这种类型。 79 | -------------------------------------------------------------------------------- /src/proc-macros/methodical/attr.md: -------------------------------------------------------------------------------- 1 | # 属性式过程宏 2 | 3 | 属性式过程宏定义了可添加到条目的的新外部属性。这种宏通过 `#[attr]` 或 4 | `#[attr(…)]` 方式调用,其中 `…` 是任意标记树。 5 | 6 | 一个属性式过程宏的简单框架如下所示: 7 | 8 | ```rs 9 | use proc_macro::TokenStream; 10 | 11 | #[proc_macro_attribute] 12 | pub fn tlborm_attribute(input: TokenStream, annotated_item: TokenStream) -> TokenStream { 13 | annotated_item 14 | } 15 | ``` 16 | 17 | 这里需要注意的是,与其他两种过程宏不同,这种宏有两个输入参数,而不是一个。 18 | 19 | * 第一个参数是属性名称后面的带分隔符的标记树,不包括它周围的分隔符。如果只有属性名称(其后不带标记树,比如 `#[attr]`),则这个参数的值为空。 20 | * 第二个参数是添加了该过程宏属性的条目,但不包括该过程宏所定义的属性。因为这是一个 [`active`] 属性,在传递给过程宏之前,该属性将从条目中剥离出来。 21 | 22 | [`active`]: https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes 23 | 24 | 返回的标记流将完全替换带被添加了该属性的条目。注意,不一定替换成单个条目,替换的结果可以是 0 或更多条目。 25 | 26 | 27 | 28 | 用法示例: 29 | 30 | ```rs 31 | use tlborm_proc::tlborm_attribute; 32 | 33 | #[tlborm_attribute] 34 | fn foo() {} 35 | 36 | #[tlborm_attribute(attributes are pretty handsome)] 37 | fn bar() {} 38 | ``` 39 | -------------------------------------------------------------------------------- /src/proc-macros/methodical/derive.md: -------------------------------------------------------------------------------- 1 | # derive 式过程宏 2 | 3 | [`derive`]: https://doc.rust-lang.org/reference/attributes/derive.html 4 | 5 | derive 式过程宏[^derive-name]为 [`derive`] 属性定义了新的输入。这种宏通过将其名称提供给 6 | `derive` 属性的输入来调用,例如 `#[derive(TlbormDerve)]`。 7 | 8 | [^derive-name]: 译者注:我通常不喜欢把 derive 翻译出来,因为它就像 trait 9 | 这个名称那样具体而明确,一目了然。当然,有时我会简写为 “derive 宏”,你称它“派生宏”也行。 10 | 11 | 一个 derive 式过程宏的简单框架如下所示: 12 | 13 | ```rs 14 | use proc_macro::TokenStream; 15 | 16 | #[proc_macro_derive(TlbormDerive)] 17 | pub fn tlborm_derive(input: TokenStream) -> TokenStream { 18 | TokenStream::new() 19 | } 20 | ``` 21 | 22 | `proc_macro_derive` 稍微特殊一些,因为它需要一个额外的标识符,此标识符将成为 derive 宏的实际名称。 23 | 24 | 输入标记流是添加了 derive 属性的条目,也就是说,它将始终是 `enum`、`struct` 或者 `union` 25 | 类型,因为这些是 derive 属性仅可以添加上去的条目。 26 | 27 | 输出的标记流将被 **追加**[^appended] 到带注释的条目所处的块或模块,所以要求标记流由一组有效条目组成。 28 | 29 | [^appended]: 译者注:属性宏与 derive 宏的显著区别在于,属性宏生成的标记是完全替换性质,而 derive 宏生成的标记是追加性质。 30 | 31 | 用法示例: 32 | 33 | ```rs 34 | use tlborm_proc::TlbormDerive; 35 | 36 | #[derive(TlbormDerive)] 37 | struct Foo; 38 | ``` 39 | 40 | # 辅助属性 41 | 42 | derive 宏又有一点特殊,因为它可以添加仅在条目定义范围内可见的附加属性。 43 | 44 | 这些属性被称为派生宏辅助属性 (*derive macro helper attributes*) ,并且是惰性的([inert])。 45 | 46 | [inert]: https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes 47 | 48 | 辅助属性的目的是在每个结构体字段或枚举体成员的基础上为 derive 宏提供额外的可定制性。 49 | 50 | 也就是说这些属性可用于附着在字段或成员上,而且不会对其本身产生影响。 51 | 52 | 又因为它们是“惰性的”,所以它们不会被剥离,并且对所有宏都可见。[^active-inert] 53 | 54 | [^active-inert]: 译者注:根据 Reference,除了属性宏的属性是 active 的,其他属性都是 inert 的。 55 | 56 | 辅助属性的定义方式是向 `proc_macro_derive` 属性增加 `attributes(helper0, helper1, ..)` 57 | 参数,该参数可包含用逗号分隔的标识符列表(即辅助属性的名称)。 58 | 59 | 因此,编写带辅助属性的 derive 宏的简单框架如下所示: 60 | 61 | ```rs 62 | use proc_macro::TokenStream; 63 | 64 | #[proc_macro_derive(TlbormDerive, attributes(tlborm_helper))] 65 | pub fn tlborm_derive(item: TokenStream) -> TokenStream { 66 | TokenStream::new() 67 | } 68 | ``` 69 | 70 | 这就是辅助属性的全部内容。在过程宏中使用(或者说消耗)辅助属性,得检查字段和成员的属性,来判断它们是否具有相应的辅助属性。 71 | 72 | 如果条目使用了所有 derive 宏都未定义的辅助属性,那么会出现错误,因为编译器会尝试将这个辅助属性解析为普通属性(而且这个属性并不存在)。 73 | 74 | 用法示例: 75 | 76 | ```rs 77 | use tlborm_proc::TlbormDerive; 78 | 79 | #[derive(TlbormDerive)] 80 | struct Foo { 81 | #[tlborm_helper] 82 | field: u32 83 | } 84 | 85 | #[derive(TlbormDerive)] 86 | enum Bar { 87 | #[tlborm_helper] 88 | Variant { #[tlborm_helper] field: u32 } 89 | } 90 | ``` 91 | -------------------------------------------------------------------------------- /src/proc-macros/methodical/function-like.md: -------------------------------------------------------------------------------- 1 | # 函数式过程宏 2 | 3 | 类似函数的过程宏,像声明宏那样被调用,即 `makro!(…)`。 4 | 5 | 不过,这类宏是三种宏中最简单的一种。它也是唯一一个在单独看调用形式时,无法与声明宏区分开的宏。 6 | 7 | 类似函数式过程宏的简单编写框架如下所示: 8 | 9 | ```rs 10 | use proc_macro::TokenStream; 11 | 12 | #[proc_macro] 13 | pub fn tlborm_fn_macro(input: TokenStream) -> TokenStream { 14 | input 15 | } 16 | ``` 17 | 18 | [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html 19 | 20 | 可以看到,这实际上只是从一个 [`TokenStream`] 到另一个 `TokenStream` 的映射,其中 21 | `input` 是调用分隔符内的标记。 22 | 23 | 例如,对于示例调用 `foo!(bar)`,输入标记流将由单独的 `bar` 标记组成。返回的标记流将替换宏调用。 24 | 25 | 这种宏类型与声明宏具有相同的放置和展开规则,即宏必须在调用位置上输出正确的标记流。 26 | 27 | 但是,与声明性宏不同,函式过程宏对其输入没有特定的限制。也就是说,在 [再谈元变量与宏展开](../../decl-macros/minutiae/metavar-and-expansion.md) 28 | 一章中列出的片段分类符跟随限制在这里不适用,因为过程宏直接作用于标记,而不是根据片段分类符或类似的东西(比如反复)匹配它们。 29 | 30 | 话虽如此,很明显,过程宏更强大,因为它们可以任意修改其输入,并生成任何所需的输出,只要输出在 Rust 的语法范围内。 31 | 32 | 用法示例: 33 | 34 | ```rs 35 | use tlborm_proc::tlborm_attribute; 36 | 37 | fn foo() { 38 | tlborm_attribute!(be quick; time is mana); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /src/proc-macros/practical.md: -------------------------------------------------------------------------------- 1 | # A Practical Introduction 2 | 3 | # 实用入门 4 | 5 | This chapter will introduce Rust's procedural macro system using 3 relatively simple, practical examples, one for each procedural macro type. 6 | 7 | 本章将使用3个相对简单、实用的例子来介绍Rust的程序性宏系统,每种程序性宏类型各一个。 8 | 9 | There is also the [proc-macro workshop](https://github.com/dtolnay/proc-macro-workshop) another high-level introduction, and the [methodical introduction](./methodical.md) chapter of this book, which explains the system without practical examples. 10 | 11 | 还有另一个高级介绍的过程-宏观研讨会,以及本书的系统介绍章节,该章节在没有实际例子的情况下解释系统。 -------------------------------------------------------------------------------- /src/proc-macros/third-party-crates.md: -------------------------------------------------------------------------------- 1 | # 第三方 crates 2 | 3 | > 注意:编写过程宏并不需要自动链接的 [`proc_macro`] crate 之外的 4 | > crate。这里列出 crates 只是使编写它们变得更简单、更简洁,同时由于增加了依赖项,可能会增加过程宏的编译时间。 5 | 6 | 过程宏放在 crate 中,因此它们可以自然地依赖于 [crates.io](https://crates.io/) 上或其他来源的 crates。 7 | 8 | crates 生态系统已经有一些非常实用、为过程宏量身定做的库,本章将快速介绍这些库,其中大部分将在接下来的章节中用来实现示例。 9 | 10 | 由于这些只是简单的介绍,如果真的需要使用,建议查看每个库的文档以获得更深入的信息。 11 | 12 | > 译者注:你可以阅读我对以下这几个库的[使用总结](https://zjp-cn.github.io/rust-note/proc-macro-note.html)。 13 | 14 | ## `proc-macro2` 15 | 16 | 你可能会认为 [`proc-macro2`] 是 [`proc_macro`] 的继承者,但实际上肯定不对! 17 | 18 | 这个名字可能有点误导,因为 [`proc-macro2`] 实际上只是对 `proc_macro` 的包装,根据其文档,它用于两个特定目的: 19 | 20 | - 将类似与过程宏的功能带到其他上下文中,如 build.rs 和 main.rs 21 | - 让过程宏可进行单元测试 22 | 23 | 由于 `proc_macro` 只能在 `proc-macro` 类型的库中使用,所以无法直接使用 `proc_macro` 库。 24 | 25 | 始终记住,`proc-macro2` 模仿 `proc_macro` 的 api,对后者进行包装,让后者的功能在非 `proc-macro` 类型的库中也能使用。 26 | 27 | 因此,建议基于 `proc-macro2` 来开发过程宏代码的库,而不是基于 `proc_macro` 28 | 构建,因为这将使这些库可以进行单元测试,这也是以下列出的库传入和返回 [`proc-macro2::TokenStream`] 的原因。 29 | 30 | 当需要 `proc_macro::TokenStream` 时,只需对 `proc-macro2::TokenStream` 进行 `.into()` 操作即可获得 `proc_macro` 的版本,反之亦然。 31 | 32 | 使用 `proc-macro2` 的过程宏通常会以别名的形式导入,比如使用 `use proc-macro2::TokenStream as TokenStream2` 来导入 `proc-macro2::TokenStream`。 33 | 34 | ## `quote` 35 | 36 | [`quote`] 主要公开了一个声明宏:[`quote!`](https://docs.rs/quote/*/quote/macro.quote.html)。 37 | 38 | 这个小小的宏让你轻松创建标记流,使用方法是将实际的源代码写出为 Rust 语法。 39 | 40 | 同时该宏还允许你将标记直接插入到编写的语法中: 41 | 1. 使用 `#local` 语法进行[插值],其中 `local` 指的是当前作用域中的一个 local。[^interpolation] 42 | 2. 使用 `#(#local)*` 来对实现了 [`ToTokens`] 的类型的迭代器进行插值,其工作原理类似于声明宏的反复,因为它们允许在反复中使用分隔符和额外的标记。 43 | 44 | [插值]: https://docs.rs/quote/*/quote/macro.quote.html#interpolation 45 | [`ToTokens`]: https://docs.rs/quote/1/quote/trait.ToTokens.html 46 | 47 | [^interpolation]: 译者注:这里的“插值”并不局限于插入“值或者表达式”,可以插入任何符合 Rust 语法的东西,比如标识符、条目、模块等等。 48 | 49 | ```rs 50 | let name = /* 某个标识符 */; 51 | let exprs = /* 某个对表达式标记流的迭代器 */; 52 | let expanded = quote! { 53 | impl SomeTrait for #name { // #name 将插入上述的局部名称 54 | fn some_function(&self) -> usize { 55 | #( #exprs )+* // 通过迭代生成表达式 56 | } 57 | } 58 | }; 59 | ``` 60 | 61 | 在准备输出时,`quote!` 是一个非常有用的工具,它避免了通过逐个插入标记来创建标记流。 62 | 63 | > 注意:如前所述,此 crate 使用 `proc_macro2`,因此 `quote!` 将返回 [`proc-macro2::TokenStream`] 类型。 64 | 65 | ## `syn` 66 | 67 | [`syn`] 是一个解析库,用于将 Rust 标记流解析为 Rust 源代码的语法树。 68 | 69 | 它是一个功能非常强大的库,使得解析过程宏输入变得非常容易,而 `proc_macro` 本身不公开任何类型的解析功能,只公开标记。 70 | 71 | 由于这个库可能是一个严重的编译依赖项,它大量使用 feature 控制来允许用户根据需要将其功能剪裁得尽可能小。 72 | 73 | 那么,它能提供什么呢?很多东西。 74 | 75 | 首先,当启用 `full` feature 时,它具有对所有标准 Rust 语法节点的定义和从而能够完全解析 Rust 语法。 76 | 77 | 在启用 `derive` feature (默认开启)之后,它还提供一个 `DeriveInput` 类型,该类型封装了传递给 derive 宏输入所有信息。 78 | 79 | 在启用 `parsing` 和 `proc-macro` feature (默认开启)之后,`DeriveInput` 可以直接与 [`parse_macro_input!`] 配合使用,以将标记流解析为所需的类型。 80 | 81 | 如果 Rust 语法不能解决你的问题,或者说你希望解析自定义的非 Rust 语法,那么这个库还提供了一个通用的[解析 API][parse],主要是以 82 | [`Parse`] trait 的形式(这需要 `parsing` feature,默认启用)。 83 | 84 | 除此之外,该库公开的类型保留了位置信息和 `Span`,这让过程宏发出详细的错误消息,指向关注点的宏输入。 85 | 86 | 由于这又是一个过程宏的库,它利用了 `proc-macro2` 的类型,因此可能需要转换成 `proc_macro` 的对应类型。 87 | 88 | > 我对 `syn` 做了更系统的梳理,你可以[阅读一下](https://zjp-cn.github.io/rust-note/proc/syn.html)。 89 | 90 | [`DeriveInput`]: https://docs.rs/syn/*/syn/struct.DeriveInput.html 91 | [`parse_macro_input!`]: https://docs.rs/syn/*/syn/macro.parse_macro_input.html 92 | [parsing API]: https://docs.rs/syn/1/syn/parse/index.html 93 | [`Parse`]: https://docs.rs/syn/1/syn/parse/trait.Parse.html 94 | 95 | [`proc_macro`]: https://doc.rust-lang.org/proc_macro/ 96 | [`proc-macro2`]: https://docs.rs/proc-macro2 97 | [`proc-macro2::TokenStream`]: https://docs.rs/proc-macro2/*/proc_macro2/struct.TokenStream.html 98 | [`quote`]: https://docs.rs/quote 99 | [`syn`]: https://docs.rs/syn 100 | -------------------------------------------------------------------------------- /src/syntax-extensions.md: -------------------------------------------------------------------------------- 1 | # 语法扩展 2 | 3 | 在讨论 Rust 的不同宏系统之前,有必要讨论一下构建它们的一般机制:语法扩展 。 4 | 5 | 要理解这一点,我们必须首先讨论编译器如何处理 Rust 6 | 源代码,以及构建用户自定义的宏和过程宏所基于的一般机制。 7 | 8 | > **注意**:本书将从现在开始,讨论 Rust 的所有不同宏类型时,使用术语“语法扩展” 9 | > (syntax extension),以减少与即将发布的声明宏 [2.0] 提案的可能存在的混淆,该提案使用了 `macro` 关键字。 10 | 11 | [2.0]: https://github.com/rust-lang/rust/issues/39412 12 | -------------------------------------------------------------------------------- /src/syntax-extensions/ast.md: -------------------------------------------------------------------------------- 1 | # AST 中的宏 2 | 3 | > 译者注:牢记“宏”是声明宏和过程宏的统称,而“宏”只是一种“语法拓展”。AST 中的宏其实围绕着标记树。 4 | 5 | 如前所述,在 Rust 中,宏处理发生**在 AST 生成之后**。因此,调用宏的语法**必须**符合 6 | Rust 语法的一部分。 7 | 8 | 实际上,Rust 语法包含数种“语法扩展”的形式。具体来说有以下四种(顺便给出例子): 9 | 10 | 1. `# [ $arg ]` 形式:比如 `#[derive(Clone)]`, `#[no_mangle]`, … 11 | 1. `# ! [ $arg ]` 形式:比如 `#![allow(dead_code)]`, `#![crate_name="blang"]`, … 12 | 1. `$name ! $arg` 形式:比如 `println!("Hi!")`, `concat!("a", "b")`, … 13 | 1. `$name ! $arg0 $arg1` 形式:比如 `macro_rules! dummy { () => {}; }`. 14 | 15 | 头两种形式被称作“属性” ([attributes])。属性用来给条目 (items) 、表达式、语句加上注解。属性有三类: 16 | - 内置的属性 ([built-in attributes]) 17 | - 过程宏属性 ([proc-macro attributes]) 18 | - 派生属性 ([derive attributes]) 19 | 20 | 内置的属性由编译器实现。过程宏属性和派生属性在 Rust 第二类宏系统 —— 过程宏 ([procedural macros]) —— 中实现。 21 | 22 | 我们感兴趣的是第 3 种:`$name ! $arg` —— 函数式 (function-like) 的宏。这种形式的宏可以通过 23 | `macro_rules!`、 `macro` 和过程宏三种方式来使用(或者说定义)。 24 | 25 | 注意第 3 种形式的函数式宏是一种一般的语法拓展形式,并非仅用 `macro_rules!` 写出。 26 | 比如 [`format!`] 是一个 `macro_rules!` 宏,而用来实现 [`format!`] 的 [`format_args!`] 不是这里谈论的宏(因为它由编译器实现,是内置的属性)。 27 | 28 | 第四种形式本质上是宏的变种。其实,这种形式的唯一用例只有 `macro_rules!`。 29 | 30 | 所以,请将注意力集中到第 3 种形式 `$name ! $arg` 上,我们的问题变成,对于每种可能的语法扩展, 31 | Rust 的语法解析器 (parser) 如何知道这里的 `$arg` 究竟长什么样? 32 | 33 | 答案是它**不需要**知道。其实,提供给语法扩展调用的参数只是**一棵**标记树 (token tree)。 34 | 35 | 具体来说,是一棵**非叶节点** (non-leaf) 的标记树:即 `(...)`、`[...]` 或 `{...}`。 36 | 37 | 知道这一点后,语法解析器如何理解如下调用形式,就变得显而易见了: 38 | 39 | ```rust,ignore 40 | bitflags! { 41 | struct Color: u8 { 42 | const RED = 0b0001, 43 | const GREEN = 0b0010, 44 | const BLUE = 0b0100, 45 | const BRIGHT = 0b1000, 46 | } 47 | } 48 | 49 | lazy_static! { 50 | static ref FIB_100: u32 = { 51 | fn fib(a: u32) -> u32 { 52 | match a { 53 | 0 => 0, 54 | 1 => 1, 55 | a => fib(a-1) + fib(a-2) 56 | } 57 | } 58 | 59 | fib(100) 60 | }; 61 | } 62 | 63 | fn main() { 64 | use Color::*; 65 | let colors = vec![RED, GREEN, BLUE]; 66 | println!("Hello, World!"); 67 | } 68 | ``` 69 | 70 | 虽然上述调用**看起来**包含了各式各样的 Rust 代码,但对语法解析器来说,它们仅仅是堆无实际意义的标记树。 71 | 72 | 为了说明问题,我们把所有这些句法“黑盒”用 ⬚ 代替,仅剩下: 73 | 74 | ```text 75 | bitflags! ⬚ 76 | 77 | lazy_static! ⬚ 78 | 79 | fn main() { 80 | let colors = vec! ⬚; 81 | println! ⬚; 82 | } 83 | ``` 84 | 85 | 再次重申,语法解析器对 ⬚ 不作任何假设;它记录黑盒所包含的标记,但并不尝试理解它们。 86 | 87 | 这意味着 ⬚ 可以是任何东西,甚至是无效的 Rust 语法。至于为什么这是好事,等会会谈到。 88 | 89 | 那么,这是否也适用于形式 1 和 2 中的 `$arg`,以及 4 中的两个参数的情况呢? 90 | 有点类似。形式 1 和 2 的 `$arg` 略有不同,因为它不是直接的标记树,而是后跟 `=` 91 | 标记加字符串表达式或标记树的**简单路径**。过程宏一章将更深入地探讨这一点。这里的重点是,该形式也使用标记树来描述输入。 92 | 93 | 第 4 种形式通常更特殊,它接受一种非常具体的语法,但这种语法也利用了标记树。这个形式下的具体情况在此处并不重要, 94 | 所以在涉及到它之前,暂时跳过它。 95 | 96 | 以下几点很重要: 97 | 98 | * Rust 包含多种语法扩展。 99 | * 当遇见形如 `$name! $arg` 的结构时,它可能是其它语法扩展,比如 `macro_rules!` 宏、过程宏甚至内置宏。 100 | * 所有 `!` 宏(即第 3 种形式)的输入都是非叶节点的单个标记树。 101 | * 语法扩展都将作为抽象语法树 (AST) 的**一部分**被解析。 102 | 103 | 最后一点最为重要,它带来了一些深远的影响。由于语法拓展将被解析进 AST 104 | 中,它们**只能**出现在那些明确支持它们出现的位置。具体来说,语法拓展能在如下位置出现: 105 | 106 | * 模式 (pattern) 107 | * 语句 (statement) 108 | * 表达式 (expression) 109 | * 条目 (item) (包括 `impl` 块) 110 | * [类型][type-macros] 111 | 112 | 一些并不支持的位置包括: 113 | 114 | * 标识符 (identifier) 115 | * `match` 分支 116 | * 结构体的字段 117 | 118 | 在上述第一个列表(支持的位置)之外,绝对没有任何地方有使用语法拓展的可能。 119 | 120 | [type-macros]: https://rust-lang.github.io/rfcs/0873-type-macros.html 121 | 122 | 123 | [attributes]: https://doc.rust-lang.org/reference/attributes.html 124 | [built-in attributes]: https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index 125 | [proc-macro attributes]: https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros 126 | [derive attributes]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-macro-helper-attributes 127 | [procedural macros]: https://doc.rust-lang.org/reference/procedural-macros.html 128 | [`format!`]: https://doc.rust-lang.org/std/macro.format.html 129 | [`format_args!`]: https://doc.rust-lang.org/std/macro.format_args.html 130 | -------------------------------------------------------------------------------- /src/syntax-extensions/debugging.md: -------------------------------------------------------------------------------- 1 | # 调试 2 | 3 | `rustc` 提供了许多工具来调试通用的语法扩展,并且针对声明宏和过程宏分别量身定制的一些更具体的工具。 4 | 5 | 有时,语法扩展所展开的内容会有问题,因为你通常看不到展开的的代码。幸运的是, `rustc` 6 | 通过不稳定的 `-Zunpretty=expanded` 参数来提供查看展开代码的功能。假设有以下代码: 7 | 8 | ```rust,ignore 9 | // Shorthand for initializing a `String`. 10 | macro_rules! S { 11 | ($e:expr) => {String::from($e)}; 12 | } 13 | 14 | fn main() { 15 | let world = S!("World"); 16 | println!("Hello, {}!", world); 17 | } 18 | ``` 19 | 20 | 使用以下命令编译: 21 | 22 | ```shell 23 | rustc +nightly -Zunpretty=expanded hello.rs 24 | ``` 25 | 26 | 生成以下输出(针对格式进行了修改): 27 | 28 | ```rust,ignore 29 | #![feature(prelude_import)] 30 | #[prelude_import] 31 | use std::prelude::rust_2018::*; 32 | #[macro_use] 33 | extern crate std; 34 | // Shorthand for initializing a `String`. 35 | macro_rules! S { ($e : expr) => { String :: from($e) } ; } 36 | 37 | fn main() { 38 | let world = String::from("World"); 39 | { 40 | ::std::io::_print( 41 | ::core::fmt::Arguments::new_v1( 42 | &["Hello, ", "!\n"], 43 | &match (&world,) { 44 | (arg0,) => [ 45 | ::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Display::fmt) 46 | ], 47 | } 48 | ) 49 | ); 50 | }; 51 | } 52 | ``` 53 | 54 | 除了 `rustc` 公开了一些方式帮助调试语法扩展之外,对于这里提到的 `-Zunpretty=expanded` 55 | 选项,由 [`dtolnay`](https://github.com/dtolnay) 制作的名为 56 | [`cargo-expand`](https://github.com/dtolnay/cargo-expand) 的 `cargo` 57 | 插件基本上对它进行了包装,使用起来更加方便。 58 | 59 | 你也可以使用 [playground](https://play.rust-lang.org/),点击右上角的 `TOOLS` 按钮来展开语法扩展! 60 | -------------------------------------------------------------------------------- /src/syntax-extensions/expansion.md: -------------------------------------------------------------------------------- 1 | # 展开 2 | 3 | 展开相对简单。在生成 AST **之后**,和编译器对程序进行语义理解之前,编译器将会对所有语法拓展进行展开。 4 | 5 | 这一过程包括:遍历 AST,确定所有语法拓展调用的位置,并把它们替换成展开的内容。 6 | 7 | 每当编译器遇见一个语法扩展,都会根据上下文解析成有限语法元素集中的一个。 8 | 9 | 举例来说,如果在模块作用域内调用语法拓展,那么编译器就将它的展开结果解析为表示某项条目 (item) 的 AST 10 | 节点;如果在表达式的位置上调用语法拓展,那么编译器就将它的展开结果解析为表示表达式的 AST 节点。 11 | 12 | 事实上,一个语义扩展的展开结果会变成以下一种情况: 13 | 14 | * 一个表达式 15 | * 一个模式 16 | * 一个类型 17 | * 零或多个条目(包括的 `impl` 块) 18 | * 零或多个语句 19 | 20 | 换句话讲,语法拓展调用所在的位置,决定了该语法拓展展开结果被解读的方式。 21 | 22 | 编译器会操作 AST 节点,把语法拓展调用处的节点完全替换成输出的节点。这一替换是结构性 (structural) 23 | 的,而非织构性 (textural) 的。 24 | 25 | 比如思考以下代码: 26 | 27 | ```rust,ignore 28 | let eight = 2 * four!(); 29 | ``` 30 | 31 | 我们可将这部分 AST 可视化地表示为: 32 | 33 | ```text 34 | ┌─────────────┐ 35 | │ Let │ 36 | │ name: eight │ ┌─────────┐ 37 | │ init: ◌ │╶─╴│ BinOp │ 38 | └─────────────┘ │ op: Mul │ 39 | ┌╴│ lhs: ◌ │ 40 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌────────────┐ 41 | │ LitInt │╶┘ └─────────┘ └╴│ Macro │ 42 | │ val: 2 │ │ name: four │ 43 | └────────┘ │ body: () │ 44 | └────────────┘ 45 | ``` 46 | 47 | 根据上下文,`four!()` **必须**展开成一个表达式(initializer 48 | 只可能是表达式)。因此,无论实际展开的结果如何,它都将被解读成一个完整的表达式。 49 | 50 | 此处假设 `four!` 成其展开结果为表达式 `1 + 3`。故而,展开后将 AST 变为: 51 | 52 | ```text 53 | ┌─────────────┐ 54 | │ Let │ 55 | │ name: eight │ ┌─────────┐ 56 | │ init: ◌ │╶─╴│ BinOp │ 57 | └─────────────┘ │ op: Mul │ 58 | ┌╴│ lhs: ◌ │ 59 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 60 | │ LitInt │╶┘ └─────────┘ └╴│ BinOp │ 61 | │ val: 2 │ │ op: Add │ 62 | └────────┘ ┌╴│ lhs: ◌ │ 63 | ┌────────┐ │ │ rhs: ◌ │╶┐ ┌────────┐ 64 | │ LitInt │╶┘ └─────────┘ └╴│ LitInt │ 65 | │ val: 1 │ │ val: 3 │ 66 | └────────┘ └────────┘ 67 | ``` 68 | 69 | 这又能被重写成: 70 | 71 | ```rust,ignore 72 | let eight = 2 * (1 + 3); 73 | ``` 74 | 75 | 注意,虽然表达式本身不包含括号,但这里仍然加上了它们。这是因为,编译器总是将语法拓展的展开结果看作完整的 76 | AST 节点,而**不是**仅仅把它视为一列标记。 77 | 78 | 换句话说,即便不显式地把复杂的表达式用括号包起来,编译器也不可能“错意”语法拓展替换的结果,或者改变求值顺序。 79 | 80 | 语法拓展被当作 AST 节点展开,这一观点非常重要,它造成两大影响: 81 | 82 | * 语法拓展不仅调用位置有限制,其展开结果也只能跟语法解析器在该位置所预期的 AST 节点种类一致。 83 | * 因此,语法拓展**必定无法**展开成不完整或不合语法的结构。 84 | 85 | 有关展开还有一点值得注意:如果某语法扩展的展开结果包含**另一个**语法扩展调用,那会怎么样? 86 | 87 | 例如,上述 `four!` 如果被展开成了 `1 + three!()`,会发生什么? 88 | 89 | ```rust,ignore 90 | let x = four!(); 91 | ``` 92 | 93 | 展开成: 94 | 95 | ```rust,ignore 96 | let x = 1 + three!(); 97 | ``` 98 | 99 | 编译器将会检查扩展结果中是否包含更多的语法拓展调用;如果有,它们将被进一步展开。 100 | 101 | 因此,上述 AST 节点将被再次展开成: 102 | 103 | ```rust,ignore 104 | let x = 1 + 3; 105 | ``` 106 | 107 | 这里的要点是,语法拓展展开发生在“传递”过程中;要完全展开所有调用,就需要同样多的传递。 108 | 109 | 嗯,也不全是如此。事实上,编译器为此设置了一个上限。它被称作语法拓展的递归上限,默认值为 128。如果第 110 | 128 次展开结果仍然包含语法拓展调用,编译器将会终止并返回一个递归上限溢出的错误信息。 111 | 112 | 此上限可通过 [`#![recursion_limit="…"]`][recursion_limit] 来提高,但这种改写必须是 crate 113 | 级别的。 一般来讲,可能的话最好还是尽量让语法拓展展开递归次数保持在默认值以下,因为会影响编译时间。 114 | 115 | [recursion_limit]: https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute 116 | -------------------------------------------------------------------------------- /src/syntax-extensions/hygiene.md: -------------------------------------------------------------------------------- 1 | # 卫生性 2 | 3 | 卫生性 (hygiene) 是宏的一个重要概念。它描述了宏在其语法上下文中工作的能力:不影响或不受其周围环境的影响。 4 | 5 | 换句话说,这意味着语法扩展应该可以在任何地方调用,而不会干扰其周围的上下文。 6 | 7 | 在一个完美的世界里,Rust 中的所有语法扩展都是完全卫生的 8 | (fully hygienic),不幸的是情况并非如此,所以应该注意避免编写不是完全卫生的语法扩展。 9 | 10 | 我们将在这里深入讨论一般的卫生性概念,这些概念会在 Rust 提供的不同语法扩展的相应的卫生性章节中涉及到。 11 | 12 | 卫生性主要影响从语法扩展产生的标识符和路径。 13 | 14 | 简而言之,如果由语法扩展创建的标识符不能被调用该语法扩展的环境访问,那么它对于该标识符是卫生的。 15 | 16 | 同样,如果语法扩展中使用的标识符不能引用到在语法扩展之外定义的内容,则被认为是卫生的。 17 | 18 | > 注意:这里说的“创建”和“使用”是指该标识所在的位置。 19 | > 20 | > 之所以说 `struct Foo{}` 中的 `Foo` 或 `let foo = …;` 中的 `foo` 21 | > 是被创建的,是因为在某种角度看,它们在其名字下引入了新的东西。 22 | > 23 | > 而之所以说 `fn foo(_: Foo) {}` 中的 `Foo` 或 `foo + 3` 中的 `foo` 24 | > 是被使用的,是因为从某种角度看,它们正指向某些已存在的东西。 25 | 26 | 最好的例子用例子来展示。假设某语法扩展 `make_local` 展开为 27 | `let local = 0;`,也就是说,它创建了标识符 `local`,然后假设有以下代码: 28 | 29 | ```rust,editable 30 | macro_rules! make_local { 31 | () => { let local = 0; } 32 | } 33 | fn main() { 34 | make_local!(); 35 | assert_eq!(local, 0); 36 | } 37 | ``` 38 | 39 | 如果 `assert_eq!(local, 0);` 中的 `local` 被解析为语法扩展所定义的 40 | `local`,则语法扩展不是卫生的(至少在 `local` 这个名称/绑定方面不是卫生的)。 41 | 42 | 现在假设有某语法扩展 `use_local` 展开为 `local = 42;`,也就是说,它使用了标识符 `local`,然后假设有以下代码: 43 | 44 | ```rust,editable 45 | // 注释这段声明宏的定义看看会发生什么 46 | macro_rules! use_local { 47 | () => { local = 42; } 48 | } 49 | fn main() { 50 | let mut local = 0; 51 | // 取消注释这段声明宏的定义看看会发生什么 52 | // macro_rules! use_local { 53 | // () => { local = 42; } 54 | // } 55 | use_local!(); 56 | } 57 | ``` 58 | 59 | 如果给定调用的语法扩展中的 `local` 被解析为调用前所定义的 `local` ,则该语法扩展也不是卫生的。 60 | 61 | 这简短地介绍了卫生性的一般概念。它将在相应的 [`macro_rules!`](../decl-macros/minutiae/hygiene.md) 62 | 和 [proc-macro](../proc-macros/hygiene.md) 章节里进行更深入的解释,并阐述其各自的特有之处。 63 | -------------------------------------------------------------------------------- /src/syntax-extensions/source-analysis.md: -------------------------------------------------------------------------------- 1 | # 源代码解析方式 2 | 3 | ## 标识化 (Tokenization) 4 | 5 | Rust程序编译过程的第一阶段是 [标记解析][tokenization]。 6 | 在这一过程中,源代码将被转换成一系列的标记 (token)。 7 | 8 | > token:无法被分割的词法单元;在编程语言世界中等价于“单词”。 9 | 10 | Rust包含多种标记,比如: 11 | 12 | * 标识符 (identifiers): `foo`, `Bambous`, `self`, `we_can_dance`, `LaCaravane`, … 13 | * 字面值 (literals): `42`, `72u32`, `0_______0`, `1.0e-40`, `"ferris was here"`, … 14 | * 关键字 (keywords): `_`, `fn`, `self`, `match`, `yield`, `macro`, … 15 | * 符号 (symbols): `[`, `:`, `::`, `?`, `~`, `@`[^wither-at], … 16 | 17 | [等等](https://doc.rust-lang.org/stable/reference/macros-by-example.html#metavariables)。 18 | 19 | 有些地方值得注意: 20 | 21 | 1. `self` 既是一个标识符又是一个关键词。 22 | 几乎在所有情况下它都被视作是一个关键词,但它有可能被视为标识符。 23 | 我们稍后会(骂骂咧咧地)提到这种情况。 24 | 25 | 2. 关键词里列有一些可疑的家伙,比如 `yield` 和 `macro`。 26 | 它们在当前的Rust语言中并没有任何含义,但编译器的确会把它们视作关键词进行解析。 27 | 这些词语被保留作语言未来扩充时使用。 28 | 29 | 3. 符号里也列有一些未被当前语言使用的条目。比如 `<-`,这是历史残留: 30 | 目前它被移除了Rust语法,但词法分析器仍然没丢掉它。 31 | 32 | 4. 注意 `::` 被视作一个独立的标记,而非两个连续的 `:` 。 33 | 这一规则适用于截至 Rust 1.2 版本的所有的多字符符号标记。 34 | [^two-lexers] 35 | 36 | [^wither-at]: `@` 被用在模式中,用来绑定模式非终止的部分到一个名称——但这似乎被大多数人完全地遗忘了。 37 | 38 | [^two-lexers]: 严格来说, Rust 1.52 版本中存在两个词法分析器 (lexer): 39 | [`rustc_lexer`] 只将单个字符作为 标记 (tokens); 40 | [`rustc_parse`] 里的 [lexer] 把多个字符作为不同的 标记 (tokens)。 41 | 42 | 作为对比,某些语言的宏系统正扎根于这一阶段。Rust并非如此。 43 | 举例来说,从效果来看,C/C++的宏就是在这里得到处理的。[^lies-damn-lies-cpp] 44 | 这也正是下列代码能够运行的原因: 45 | [^cpp-it-seemed-like-a-good-idea-at-the-time] 46 | 47 | ```c 48 | #define SUB void 49 | #define BEGIN { 50 | #define END } 51 | 52 | SUB main() BEGIN 53 | printf("Oh, the horror!\n"); 54 | END 55 | ``` 56 | 57 | [^lies-damn-lies-cpp]: 58 | 实际上,C 预处理程序使用与 C 自身所不同的词法结构,但这些区别很大程度上无关紧要。 59 | 60 | [^cpp-it-seemed-like-a-good-idea-at-the-time]: 61 | 是否应该这样运行完全是一个另外的话题了。 62 | 63 | ## 语法解析 (Parsing) 64 | 65 | 编译过程的下一个阶段是语法解析 (parsing)。 66 | 67 | 这一过程中,一系列的 token 将被转换成一棵抽象语法树 (AST: [Abstract Syntax Tree](AST))。 68 | 此过程将在内存中建立起程序的语法结构。 69 | 70 | 举例来说,标记序列 `1+2` 将被转换成某种类似于: 71 | 72 | ```text 73 | ┌─────────┐ ┌─────────┐ 74 | │ BinOp │ ┌╴│ LitInt │ 75 | │ op: Add │ │ │ val: 1 │ 76 | │ lhs: ◌ │╶┘ └─────────┘ 77 | │ rhs: ◌ │╶┐ ┌─────────┐ 78 | └─────────┘ └╴│ LitInt │ 79 | │ val: 2 │ 80 | └─────────┘ 81 | ``` 82 | 83 | AST 将包含 **整个** 程序的结构,但这一结构仅包含词法信息。 84 | 85 | 举例来讲,在这个阶段编译器虽然可能知道某个表达式提及了某个名为 `a` 的变量, 86 | 但它并 **没有办法知道** `a` 究竟是什么,或者它从哪来。 87 | 88 | 在 AST 生成之后,宏处理过程才开始。 89 | 但在讨论宏处理过程之前,我们需要先谈谈标记树 (token tree)。 90 | 91 | ## 标记树 (Token Trees) 92 | 93 | 标记树是介于 标记 (token) 与 AST 之间的东西。 94 | 95 | 首先明确一点,几乎所有标记都构成标记树。 96 | 具体来说,它们可被看作标记树叶节点。 97 | 还有另一类事物也可被看作标记树叶节点,我们将在稍后提到它。 98 | 99 | 仅有的一种基础标记不是标记树叶节点——“分组”标记:`(...)`, `[...]` 和 `{...}`。 100 | 这三者属于标记树内的节点,正是它们给标记树带来了树状的结构。 101 | 102 | 给个具体的例子,这列标记: 103 | 104 | ```text 105 | a + b + (c + d[0]) + e 106 | ``` 107 | 108 | 将被解析为这样的标记树: 109 | 110 | ```text 111 | «a» «+» «b» «+» «( )» «+» «e» 112 | ╭────────┴──────────╮ 113 | «c» «+» «d» «[ ]» 114 | ╭─┴─╮ 115 | «0» 116 | ``` 117 | 118 | 注意它跟最后生成的 AST 并 *没有关联*。 119 | AST 将仅有一个根节点,而这棵标记树有 *七* 个。 120 | 作为参考,最后生成的 AST 应该是这样: 121 | 122 | ```text 123 | ┌─────────┐ 124 | │ BinOp │ 125 | │ op: Add │ 126 | ┌╴│ lhs: ◌ │ 127 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 128 | │ Var │╶┘ └─────────┘ └╴│ BinOp │ 129 | │ name: a │ │ op: Add │ 130 | └─────────┘ ┌╴│ lhs: ◌ │ 131 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 132 | │ Var │╶┘ └─────────┘ └╴│ BinOp │ 133 | │ name: b │ │ op: Add │ 134 | └─────────┘ ┌╴│ lhs: ◌ │ 135 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 136 | │ BinOp │╶┘ └─────────┘ └╴│ Var │ 137 | │ op: Add │ │ name: e │ 138 | ┌╴│ lhs: ◌ │ └─────────┘ 139 | ┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐ 140 | │ Var │╶┘ └─────────┘ └╴│ Index │ 141 | │ name: c │ ┌╴│ arr: ◌ │ 142 | └─────────┘ ┌─────────┐ │ │ ind: ◌ │╶┐ ┌─────────┐ 143 | │ Var │╶┘ └─────────┘ └╴│ LitInt │ 144 | │ name: d │ │ val: 0 │ 145 | └─────────┘ └─────────┘ 146 | ``` 147 | 148 | 理解 AST 与 标记树 (token tree) 之间的区别至关重要。 149 | 写宏时,你将同时与这两者打交道。 150 | 151 | 还有一条需要注意:*不可能* 出现不匹配的小/中/大括号,也不可能存在包含错误嵌套结构的标记树。 152 | 153 | [tokenization]: https://en.wikipedia.org/wiki/Lexical_analysis#Tokenization 154 | [reserved]: https://doc.rust-lang.org/reference/keywords.html#reserved-keywords 155 | [`rustc_lexer`]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_lexer 156 | [`rustc_parse`]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_parse 157 | [lexer]: https://github.com/rust-lang/rust/tree/master/compiler/rustc_parse/src/lexer 158 | [Abstract Syntax Tree]: https://en.wikipedia.org/wiki/Abstract_syntax_tree 159 | -------------------------------------------------------------------------------- /src/translation_statement.md: -------------------------------------------------------------------------------- 1 | # 翻译说明 2 | 3 | 本书是续写版本,续写的版本由 Veykril 撰稿,续作对原作有补充和删改。 4 | 5 | - 原作:[repo](https://github.com/DanielKeep/tlborm) | [渲染版](https://danielkeep.github.io/tlborm/) 6 | - 原作中文翻译:[repo](https://github.com/DaseinPhaos/tlborm) | [渲染版](https://www.bookstack.cn/read/DaseinPhaos-tlborm-chinese/README.md) 7 | 8 | 原作及其翻译渲染版本没有使用 mdbook 构建,而是使用 py 来生成 HTML。 9 | 在发布文档和运行样板代码方面诸多不便。 10 | 11 | 而且由于原作在 2016 年没再更新,其内容基于 Rust 2015 版本, 12 | 续写的版本也只是把过时的细节更新至 2018 之后的版本。 13 | 14 | 我认为这本书在阐述 **声明宏** 方面搭建了一个很小巧精美的骨架,过程宏的资料比较丰富,而且过程宏生态主要围绕[第三方库](proc-macros/third-party-crates.md)开展。 15 | 16 | 续作及本翻译渲染版本使用 mdbook 构建: 17 | - 续作:[repo](https://github.com/veykril/tlborm) | [渲染版](https://veykril.github.io/tlborm/) 18 | - 续作中文翻译:[repo](https://github.com/zjp-CN/tlborm) | [渲染版](https://zjp-cn.github.io/tlborm) 19 | 20 | 另外,此翻译版本提供的阅读功能: 21 | 1. 行间代码块大部分可以点击右上角按钮运行,有些可以 **编辑** 和运行 22 | (目的是快速而方便地验证读者思考的代码能否编译通过)。 23 | 只用于展示说明、或者不适合运行的代码只有复制按钮。 24 | 25 | > 区分能编辑代码块的方法:光标能够在代码块中停留和闪动;有同级竖线;右上角有 26 | > undo 图标;选中代码时背景色较浅;看代码块的主题颜色。 27 | 28 | 2. 每个页面右侧都有本章节的 **大纲目录** ,可以点击跳转。 29 | 如果大纲目录显示不完整,可以缩小浏览器页面;或者收起左侧的章节目录。 30 | 大纲目录仅在电脑网页版生效,移动端网页不会显示。 31 | 32 | 3. 所有 [`code`](#) 蓝色样式、光标移上去有下划线的内容(普通正文或者行内代码)都是链接,可以跳转。 33 | 无链接的行内代码样式是这样的:`code` 。 34 | 35 | 4. 翻译专有名词时,给出原英文,因为我认为那些词语是初次阅读英文时的障碍, 36 | 所以当读者查阅其他英文资料时,就不会感到陌生了。 37 | 38 | # 更新日志 39 | 40 | ## 2022.04 41 | 42 | 主要补充的部分在于: 43 | - [元变量表达式](./decl-macros/minutiae/metavar-expr.html) 44 | - [macro 2.0](./decl-macros/macros2.html) 45 | - [模式:TT “撕咬机”](./decl-macros/patterns/tt-muncher.html#性能建议) 之类的“模式”章节添加了“性能建议” 46 | - [过程宏](./proc-macros.html) 47 | 48 | ## 2021.06 49 | 50 | 主要补充的部分在于: 51 | 52 | - [元变量 (metavariables)](./decl-macros/macros-methodical.html#元变量) 53 | - [片段分类符 (fragment-specifiers)](./decl-macros/minutiae/fragment-specifiers.md) 54 | - [调试](./decl-macros/minutiae/debugging.md) 55 | - [作用域](./decl-macros/minutiae/scoping.md) 56 | - [导入/导出宏](./decl-macros/minutiae/import-export.md) 57 | - [计数:bit twiddling](./decl-macros/building-blocks/counting.md#bit-twiddling) 58 | - 译者补充:[算盘游戏](./decl-macros/building-blocks/abacus-counting.md#算盘游戏) 59 | - [构件:解析](./decl-macros/building-blocks/parsing.md) 60 | 61 | -------------------------------------------------------------------------------- /theme/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | html { 4 | scrollbar-color: var(--scrollbar) var(--bg); 5 | } 6 | #searchresults a, 7 | .content a:link, 8 | a:visited, 9 | a > .hljs { 10 | color: var(--links); 11 | } 12 | 13 | /* 14 | body-container is necessary because mobile browsers don't seem to like 15 | overflow-x on the body tag when there is a tag. 16 | */ 17 | #body-container { 18 | /* 19 | This is used when the sidebar pushes the body content off the side of 20 | the screen on small screens. Without it, dragging on mobile Safari 21 | will want to reposition the viewport in a weird way. 22 | */ 23 | overflow-x: clip; 24 | } 25 | 26 | /* Menu Bar */ 27 | 28 | #menu-bar, 29 | #menu-bar-hover-placeholder { 30 | z-index: 101; 31 | margin: auto calc(0px - var(--page-padding)); 32 | } 33 | #menu-bar { 34 | position: relative; 35 | display: flex; 36 | flex-wrap: wrap; 37 | background-color: var(--bg); 38 | border-block-end-color: var(--bg); 39 | border-block-end-width: 1px; 40 | border-block-end-style: solid; 41 | } 42 | #menu-bar.sticky, 43 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 44 | .js #menu-bar:hover, 45 | .js.sidebar-visible #menu-bar { 46 | position: -webkit-sticky; 47 | position: sticky; 48 | top: 0 !important; 49 | } 50 | #menu-bar-hover-placeholder { 51 | position: sticky; 52 | position: -webkit-sticky; 53 | top: 0; 54 | height: var(--menu-bar-height); 55 | } 56 | #menu-bar.bordered { 57 | border-block-end-color: var(--table-border-color); 58 | } 59 | #menu-bar i, #menu-bar .icon-button { 60 | position: relative; 61 | padding: 0 8px; 62 | z-index: 10; 63 | line-height: var(--menu-bar-height); 64 | cursor: pointer; 65 | transition: color 0.5s; 66 | } 67 | @media only screen and (max-width: 420px) { 68 | #menu-bar i, #menu-bar .icon-button { 69 | padding: 0 5px; 70 | } 71 | } 72 | 73 | .icon-button { 74 | border: none; 75 | background: none; 76 | padding: 0; 77 | color: inherit; 78 | } 79 | .icon-button i { 80 | margin: 0; 81 | } 82 | 83 | .right-buttons { 84 | margin: 0 15px; 85 | } 86 | .right-buttons a { 87 | text-decoration: none; 88 | } 89 | 90 | .left-buttons { 91 | display: flex; 92 | margin: 0 5px; 93 | } 94 | .no-js .left-buttons button { 95 | display: none; 96 | } 97 | 98 | .menu-title { 99 | display: inline-block; 100 | font-weight: 200; 101 | font-size: 2.4rem; 102 | line-height: var(--menu-bar-height); 103 | text-align: center; 104 | margin: 0; 105 | flex: 1; 106 | white-space: nowrap; 107 | overflow: hidden; 108 | text-overflow: ellipsis; 109 | } 110 | .js .menu-title { 111 | cursor: pointer; 112 | } 113 | 114 | .menu-bar, 115 | .menu-bar:visited, 116 | .nav-chapters, 117 | .nav-chapters:visited, 118 | .mobile-nav-chapters, 119 | .mobile-nav-chapters:visited, 120 | .menu-bar .icon-button, 121 | .menu-bar a i { 122 | color: var(--icons); 123 | } 124 | 125 | .menu-bar i:hover, 126 | .menu-bar .icon-button:hover, 127 | .nav-chapters:hover, 128 | .mobile-nav-chapters i:hover { 129 | color: var(--icons-hover); 130 | } 131 | 132 | /* Nav Icons */ 133 | 134 | .nav-chapters { 135 | font-size: 2.5em; 136 | text-align: center; 137 | text-decoration: none; 138 | 139 | position: fixed; 140 | top: 0; 141 | bottom: 0; 142 | margin: 0; 143 | max-width: 3%; 144 | min-width: 3%; 145 | 146 | display: flex; 147 | justify-content: center; 148 | align-content: center; 149 | flex-direction: column; 150 | 151 | transition: color 0.5s, background-color 0.5s; 152 | } 153 | 154 | .nav-chapters:hover { 155 | text-decoration: none; 156 | background-color: var(--theme-hover); 157 | transition: background-color 0.15s, color 0.15s; 158 | } 159 | 160 | .nav-wrapper { 161 | margin-block-start: 50px; 162 | display: none; 163 | } 164 | 165 | .mobile-nav-chapters { 166 | font-size: 2.5em; 167 | text-align: center; 168 | text-decoration: none; 169 | width: 90px; 170 | border-radius: 5px; 171 | background-color: var(--sidebar-bg); 172 | } 173 | 174 | /* Only Firefox supports flow-relative values */ 175 | .previous { float: left; } 176 | [dir=rtl] .previous { float: right; } 177 | 178 | /* Only Firefox supports flow-relative values */ 179 | .next { 180 | float: right; 181 | right: var(--page-padding); 182 | } 183 | [dir=rtl] .next { 184 | float: left; 185 | right: unset; 186 | left: var(--page-padding); 187 | } 188 | 189 | /* Use the correct buttons for RTL layouts*/ 190 | [dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} 191 | [dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } 192 | 193 | @media only screen and (max-width: 1080px) { 194 | .nav-wide-wrapper { display: none; } 195 | .nav-wrapper { display: block; } 196 | } 197 | 198 | /* sidebar-visible */ 199 | @media only screen and (max-width: 1380px) { 200 | #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } 201 | #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } 202 | } 203 | 204 | /* Inline code */ 205 | 206 | :not(pre) > .hljs { 207 | display: inline; 208 | padding: 0.1em 0.3em; 209 | border-radius: 3px; 210 | } 211 | 212 | :not(pre):not(a) > .hljs { 213 | color: var(--inline-code-color); 214 | overflow-x: initial; 215 | } 216 | 217 | a:hover > .hljs { 218 | text-decoration: underline; 219 | } 220 | 221 | pre { 222 | position: relative; 223 | } 224 | pre > .buttons { 225 | position: absolute; 226 | z-index: 100; 227 | right: 0px; 228 | top: 2px; 229 | margin: 0px; 230 | padding: 2px 0px; 231 | 232 | color: var(--sidebar-fg); 233 | cursor: pointer; 234 | visibility: hidden; 235 | opacity: 0; 236 | transition: visibility 0.1s linear, opacity 0.1s linear; 237 | } 238 | pre:hover > .buttons { 239 | visibility: visible; 240 | opacity: 1 241 | } 242 | pre > .buttons :hover { 243 | color: var(--sidebar-active); 244 | border-color: var(--icons-hover); 245 | background-color: var(--theme-hover); 246 | } 247 | pre > .buttons i { 248 | margin-inline-start: 8px; 249 | } 250 | pre > .buttons button { 251 | cursor: inherit; 252 | margin: 0px 5px; 253 | padding: 3px 5px; 254 | font-size: 14px; 255 | 256 | border-style: solid; 257 | border-width: 1px; 258 | border-radius: 4px; 259 | border-color: var(--icons); 260 | background-color: var(--theme-popup-bg); 261 | transition: 100ms; 262 | transition-property: color,border-color,background-color; 263 | color: var(--icons); 264 | } 265 | @media (pointer: coarse) { 266 | pre > .buttons button { 267 | /* On mobile, make it easier to tap buttons. */ 268 | padding: 0.3rem 1rem; 269 | } 270 | 271 | .sidebar-resize-indicator { 272 | /* Hide resize indicator on devices with limited accuracy */ 273 | display: none; 274 | } 275 | } 276 | pre > code { 277 | display: block; 278 | padding: 1rem; 279 | } 280 | 281 | /* FIXME: ACE editors overlap their buttons because ACE does absolute 282 | positioning within the code block which breaks padding. The only solution I 283 | can think of is to move the padding to the outer pre tag (or insert a div 284 | wrapper), but that would require fixing a whole bunch of CSS rules. 285 | */ 286 | .hljs.ace_editor { 287 | padding: 0rem 0rem; 288 | } 289 | 290 | pre > .result { 291 | margin-block-start: 10px; 292 | } 293 | 294 | /* Search */ 295 | 296 | #searchresults a { 297 | text-decoration: none; 298 | } 299 | 300 | mark { 301 | border-radius: 2px; 302 | padding-block-start: 0; 303 | padding-block-end: 1px; 304 | padding-inline-start: 3px; 305 | padding-inline-end: 3px; 306 | margin-block-start: 0; 307 | margin-block-end: -1px; 308 | margin-inline-start: -3px; 309 | margin-inline-end: -3px; 310 | background-color: var(--search-mark-bg); 311 | transition: background-color 300ms linear; 312 | cursor: pointer; 313 | } 314 | 315 | mark.fade-out { 316 | background-color: rgba(0,0,0,0) !important; 317 | cursor: auto; 318 | } 319 | 320 | .searchbar-outer { 321 | margin-inline-start: auto; 322 | margin-inline-end: auto; 323 | max-width: var(--content-max-width); 324 | } 325 | 326 | #searchbar { 327 | width: 100%; 328 | margin-block-start: 5px; 329 | margin-block-end: 0; 330 | margin-inline-start: auto; 331 | margin-inline-end: auto; 332 | padding: 10px 16px; 333 | transition: box-shadow 300ms ease-in-out; 334 | border: 1px solid var(--searchbar-border-color); 335 | border-radius: 3px; 336 | background-color: var(--searchbar-bg); 337 | color: var(--searchbar-fg); 338 | } 339 | #searchbar:focus, 340 | #searchbar.active { 341 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 342 | } 343 | 344 | .searchresults-header { 345 | font-weight: bold; 346 | font-size: 1em; 347 | padding-block-start: 18px; 348 | padding-block-end: 0; 349 | padding-inline-start: 5px; 350 | padding-inline-end: 0; 351 | color: var(--searchresults-header-fg); 352 | } 353 | 354 | .searchresults-outer { 355 | margin-inline-start: auto; 356 | margin-inline-end: auto; 357 | max-width: var(--content-max-width); 358 | border-block-end: 1px dashed var(--searchresults-border-color); 359 | } 360 | 361 | ul#searchresults { 362 | list-style: none; 363 | padding-inline-start: 20px; 364 | } 365 | ul#searchresults li { 366 | margin: 10px 0px; 367 | padding: 2px; 368 | border-radius: 2px; 369 | } 370 | ul#searchresults li.focus { 371 | background-color: var(--searchresults-li-bg); 372 | } 373 | ul#searchresults span.teaser { 374 | display: block; 375 | clear: both; 376 | margin-block-start: 5px; 377 | margin-block-end: 0; 378 | margin-inline-start: 20px; 379 | margin-inline-end: 0; 380 | font-size: 0.8em; 381 | } 382 | ul#searchresults span.teaser em { 383 | font-weight: bold; 384 | font-style: normal; 385 | } 386 | 387 | /* Sidebar */ 388 | 389 | .sidebar { 390 | position: fixed; 391 | left: 0; 392 | top: 0; 393 | bottom: 0; 394 | width: var(--sidebar-width); 395 | font-size: 0.85em; 396 | box-sizing: border-box; 397 | -webkit-overflow-scrolling: touch; 398 | overscroll-behavior-y: contain; 399 | background-color: var(--sidebar-bg); 400 | color: var(--sidebar-fg); 401 | } 402 | [dir=rtl] .sidebar { left: unset; right: 0; } 403 | .sidebar-resizing { 404 | -moz-user-select: none; 405 | -webkit-user-select: none; 406 | -ms-user-select: none; 407 | user-select: none; 408 | } 409 | .no-js .sidebar, 410 | .js:not(.sidebar-resizing) .sidebar { 411 | transition: transform 0.3s; /* Animation: slide away */ 412 | } 413 | .sidebar code { 414 | line-height: 2em; 415 | } 416 | .sidebar .sidebar-scrollbox { 417 | overflow-y: auto; 418 | position: absolute; 419 | top: 0; 420 | bottom: 0; 421 | left: 0; 422 | right: 0; 423 | padding: 10px 10px; 424 | } 425 | .sidebar .sidebar-resize-handle { 426 | position: absolute; 427 | cursor: col-resize; 428 | width: 0; 429 | right: calc(var(--sidebar-resize-indicator-width) * -1); 430 | top: 0; 431 | bottom: 0; 432 | display: flex; 433 | align-items: center; 434 | } 435 | 436 | .sidebar-resize-handle .sidebar-resize-indicator { 437 | width: 100%; 438 | height: 12px; 439 | background-color: var(--icons); 440 | margin-inline-start: var(--sidebar-resize-indicator-space); 441 | } 442 | 443 | [dir=rtl] .sidebar .sidebar-resize-handle { 444 | left: calc(var(--sidebar-resize-indicator-width) * -1); 445 | right: unset; 446 | } 447 | .js .sidebar .sidebar-resize-handle { 448 | cursor: col-resize; 449 | width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space)); 450 | } 451 | /* sidebar-hidden */ 452 | #sidebar-toggle-anchor:not(:checked) ~ .sidebar { 453 | transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); 454 | z-index: -1; 455 | } 456 | [dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { 457 | transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); 458 | } 459 | .sidebar::-webkit-scrollbar { 460 | background: var(--sidebar-bg); 461 | } 462 | .sidebar::-webkit-scrollbar-thumb { 463 | background: var(--scrollbar); 464 | } 465 | 466 | /* sidebar-visible */ 467 | #sidebar-toggle-anchor:checked ~ .page-wrapper { 468 | transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width))); 469 | } 470 | [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { 471 | transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width))); 472 | } 473 | @media only screen and (min-width: 620px) { 474 | #sidebar-toggle-anchor:checked ~ .page-wrapper { 475 | transform: none; 476 | margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)); 477 | } 478 | [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { 479 | transform: none; 480 | } 481 | } 482 | 483 | .chapter { 484 | list-style: none outside none; 485 | padding-inline-start: 0; 486 | line-height: 2em; 487 | } 488 | 489 | .chapter ol { 490 | width: 100%; 491 | } 492 | 493 | .chapter li { 494 | display: flex; 495 | color: var(--sidebar-non-existant); 496 | } 497 | .chapter li a { 498 | display: block; 499 | padding: 0; 500 | text-decoration: none; 501 | color: var(--sidebar-fg); 502 | } 503 | 504 | .chapter li a:hover { 505 | color: var(--sidebar-active); 506 | } 507 | 508 | .chapter li a.active { 509 | color: var(--sidebar-active); 510 | } 511 | 512 | .chapter li > a.toggle { 513 | cursor: pointer; 514 | display: block; 515 | margin-inline-start: auto; 516 | padding: 0 10px; 517 | user-select: none; 518 | opacity: 0.68; 519 | } 520 | 521 | .chapter li > a.toggle div { 522 | transition: transform 0.5s; 523 | } 524 | 525 | /* collapse the section */ 526 | .chapter li:not(.expanded) + li > ol { 527 | display: none; 528 | } 529 | 530 | .chapter li.chapter-item { 531 | line-height: 1.5em; 532 | margin-block-start: 0.6em; 533 | } 534 | 535 | .chapter li.expanded > a.toggle div { 536 | transform: rotate(90deg); 537 | } 538 | 539 | .spacer { 540 | width: 100%; 541 | height: 3px; 542 | margin: 5px 0px; 543 | } 544 | .chapter .spacer { 545 | background-color: var(--sidebar-spacer); 546 | } 547 | 548 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 549 | .chapter li a { padding: 5px 0; } 550 | .spacer { margin: 10px 0; } 551 | } 552 | 553 | .section { 554 | list-style: none outside none; 555 | padding-inline-start: 20px; 556 | line-height: 1.5em; 557 | } 558 | 559 | /* Theme Menu Popup */ 560 | 561 | .theme-popup { 562 | position: absolute; 563 | left: 10px; 564 | top: var(--menu-bar-height); 565 | z-index: 1000; 566 | border-radius: 4px; 567 | font-size: 0.7em; 568 | color: var(--fg); 569 | background: var(--theme-popup-bg); 570 | border: 1px solid var(--theme-popup-border); 571 | margin: 0; 572 | padding: 0; 573 | list-style: none; 574 | display: none; 575 | /* Don't let the children's background extend past the rounded corners. */ 576 | overflow: hidden; 577 | } 578 | [dir=rtl] .theme-popup { left: unset; right: 10px; } 579 | .theme-popup .default { 580 | color: var(--icons); 581 | } 582 | .theme-popup .theme { 583 | width: 100%; 584 | border: 0; 585 | margin: 0; 586 | padding: 2px 20px; 587 | line-height: 25px; 588 | white-space: nowrap; 589 | text-align: start; 590 | cursor: pointer; 591 | color: inherit; 592 | background: inherit; 593 | font-size: inherit; 594 | } 595 | .theme-popup .theme:hover { 596 | background-color: var(--theme-hover); 597 | } 598 | 599 | .theme-selected::before { 600 | display: inline-block; 601 | content: "✓"; 602 | margin-inline-start: -14px; 603 | width: 14px; 604 | } 605 | -------------------------------------------------------------------------------- /theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | :root { 4 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 5 | font-size: 70%; 6 | color-scheme: var(--color-scheme); 7 | } 8 | 9 | html { 10 | font-family: "Open Sans", sans-serif; 11 | color: var(--fg); 12 | background-color: var(--bg); 13 | text-size-adjust: none; 14 | -webkit-text-size-adjust: none; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | font-size: 1.5rem; 20 | overflow-x: hidden; 21 | } 22 | 23 | code { 24 | font-family: var(--mono-font) !important; 25 | font-size: 0.9em; 26 | direction: ltr !important; 27 | } 28 | 29 | /* make long words/inline code not x overflow */ 30 | main { 31 | overflow-wrap: break-word; 32 | } 33 | 34 | /* make wide tables scroll if they overflow */ 35 | .table-wrapper { 36 | overflow-x: auto; 37 | } 38 | 39 | /* Don't change font size in headers. */ 40 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 41 | font-size: unset; 42 | } 43 | 44 | .left { float: left; } 45 | .right { float: right; } 46 | .boring { opacity: 0.6; } 47 | .hide-boring .boring { display: none; } 48 | .hidden { display: none !important; } 49 | 50 | h2, h3 { margin-block-start: 2.5em; } 51 | h4, h5 { margin-block-start: 2em; } 52 | 53 | .header + .header h3, 54 | .header + .header h4, 55 | .header + .header h5 { 56 | margin-block-start: 1em; 57 | } 58 | 59 | h1:target::before, 60 | h2:target::before, 61 | h3:target::before, 62 | h4:target::before, 63 | h5:target::before, 64 | h6:target::before { 65 | display: inline-block; 66 | content: "»"; 67 | margin-inline-start: -30px; 68 | width: 30px; 69 | } 70 | 71 | /* This is broken on Safari as of version 14, but is fixed 72 | in Safari Technology Preview 117 which I think will be Safari 14.2. 73 | https://bugs.webkit.org/show_bug.cgi?id=218076 74 | */ 75 | :target { 76 | /* Safari does not support logical properties */ 77 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 78 | } 79 | 80 | .page { 81 | outline: 0; 82 | padding: 0 var(--page-padding); 83 | margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 84 | } 85 | .page-wrapper { 86 | box-sizing: border-box; 87 | background-color: var(--bg); 88 | } 89 | .no-js .page-wrapper, 90 | .js:not(.sidebar-resizing) .page-wrapper { 91 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 92 | } 93 | [dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { 94 | transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 95 | } 96 | 97 | .content { 98 | overflow-y: auto; 99 | padding: 0 10px; 100 | } 101 | .content main { 102 | margin-inline-start: auto; 103 | margin-inline-end: auto; 104 | max-width: var(--content-max-width); 105 | } 106 | .content p { line-height: 1.45em; } 107 | .content ol { line-height: 1.45em; } 108 | .content ul { line-height: 1.45em; } 109 | .content a { text-decoration: none; } 110 | .content a:hover { text-decoration: underline; } 111 | .content img, .content video { max-width: 100%; } 112 | .content .header:link, 113 | .content .header:visited { 114 | color: var(--fg); 115 | } 116 | .content .header:link, 117 | .content .header:visited:hover { 118 | text-decoration: none; 119 | } 120 | 121 | table { 122 | margin: 0 auto; 123 | border-collapse: collapse; 124 | } 125 | table td { 126 | padding: 3px 20px; 127 | border: 1px var(--table-border-color) solid; 128 | } 129 | table thead { 130 | background: var(--table-header-bg); 131 | } 132 | table thead td { 133 | font-weight: 700; 134 | border: none; 135 | } 136 | table thead th { 137 | padding: 3px 20px; 138 | } 139 | table thead tr { 140 | border: 1px var(--table-header-bg) solid; 141 | } 142 | /* Alternate background colors for rows */ 143 | table tbody tr:nth-child(2n) { 144 | background: var(--table-alternate-bg); 145 | } 146 | 147 | 148 | blockquote { 149 | margin: 20px 0; 150 | padding: 0 20px; 151 | color: var(--fg); 152 | background-color: var(--quote-bg); 153 | border-block-start: .1em solid var(--quote-border); 154 | border-block-end: .1em solid var(--quote-border); 155 | } 156 | 157 | .warning { 158 | margin: 20px; 159 | padding: 0 20px; 160 | border-inline-start: 2px solid var(--warning-border); 161 | } 162 | 163 | .warning:before { 164 | position: absolute; 165 | width: 3rem; 166 | height: 3rem; 167 | margin-inline-start: calc(-1.5rem - 21px); 168 | content: "ⓘ"; 169 | text-align: center; 170 | background-color: var(--bg); 171 | color: var(--warning-border); 172 | font-weight: bold; 173 | font-size: 2rem; 174 | } 175 | 176 | blockquote .warning:before { 177 | background-color: var(--quote-bg); 178 | } 179 | 180 | kbd { 181 | background-color: var(--table-border-color); 182 | border-radius: 4px; 183 | border: solid 1px var(--theme-popup-border); 184 | box-shadow: inset 0 -1px 0 var(--theme-hover); 185 | display: inline-block; 186 | font-size: var(--code-font-size); 187 | font-family: var(--mono-font); 188 | line-height: 10px; 189 | padding: 4px 5px; 190 | vertical-align: middle; 191 | } 192 | 193 | :not(.footnote-definition) + .footnote-definition, 194 | .footnote-definition + :not(.footnote-definition) { 195 | margin-block-start: 2em; 196 | } 197 | .footnote-definition { 198 | font-size: 0.9em; 199 | margin: 0.5em 0; 200 | } 201 | .footnote-definition p { 202 | display: inline; 203 | } 204 | 205 | .tooltiptext { 206 | position: absolute; 207 | visibility: hidden; 208 | color: #fff; 209 | background-color: #333; 210 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 211 | left: -8px; /* Half of the width of the icon */ 212 | top: -35px; 213 | font-size: 0.8em; 214 | text-align: center; 215 | border-radius: 6px; 216 | padding: 5px 8px; 217 | margin: 5px; 218 | z-index: 1000; 219 | } 220 | .tooltipped .tooltiptext { 221 | visibility: visible; 222 | } 223 | 224 | .chapter li.part-title { 225 | color: var(--sidebar-fg); 226 | margin: 5px 0px; 227 | font-weight: bold; 228 | } 229 | 230 | .result-no-output { 231 | font-style: italic; 232 | } 233 | -------------------------------------------------------------------------------- /theme/css/variables.css: -------------------------------------------------------------------------------- 1 | 2 | /* Globals */ 3 | 4 | :root { 5 | --sidebar-width: 255px; 6 | --sidebar-resize-indicator-width: 8px; 7 | --sidebar-resize-indicator-space: 2px; 8 | --page-padding: 15px; 9 | --content-max-width: 78%; 10 | --menu-bar-height: 40px; 11 | --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; 12 | --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ 13 | --pagetoc-width: 12%; 14 | --pagetoc-fontsize: 14.5px; 15 | } 16 | 17 | @media only screen and (max-width:1439px) { 18 | :root{ 19 | --content-max-width: 98%; 20 | } 21 | } 22 | 23 | /* Themes */ 24 | 25 | .ayu { 26 | --bg: hsl(210, 25%, 8%); 27 | --fg: #c5c5c5; 28 | 29 | --sidebar-bg: #14191f; 30 | --sidebar-fg: #c8c9db; 31 | --sidebar-non-existant: #5c6773; 32 | --sidebar-active: #ffb454; 33 | --sidebar-spacer: #2d334f; 34 | 35 | --scrollbar: var(--sidebar-fg); 36 | 37 | --icons: #737480; 38 | --icons-hover: #b7b9cc; 39 | 40 | --links: #0096cf; 41 | 42 | --inline-code-color: #ffb454; 43 | 44 | --theme-popup-bg: #14191f; 45 | --theme-popup-border: #5c6773; 46 | --theme-hover: #191f26; 47 | 48 | --quote-bg: hsl(226, 15%, 17%); 49 | --quote-border: hsl(226, 15%, 22%); 50 | 51 | --warning-border: #ff8e00; 52 | 53 | --table-border-color: hsl(210, 25%, 13%); 54 | --table-header-bg: hsl(210, 25%, 28%); 55 | --table-alternate-bg: hsl(210, 25%, 11%); 56 | 57 | --searchbar-border-color: #848484; 58 | --searchbar-bg: #424242; 59 | --searchbar-fg: #fff; 60 | --searchbar-shadow-color: #d4c89f; 61 | --searchresults-header-fg: #666; 62 | --searchresults-border-color: #888; 63 | --searchresults-li-bg: #252932; 64 | --search-mark-bg: #e3b171; 65 | 66 | --color-scheme: dark; 67 | } 68 | 69 | .coal { 70 | --bg: hsl(200, 7%, 8%); 71 | --fg: #98a3ad; 72 | 73 | --sidebar-bg: #292c2f; 74 | --sidebar-fg: #a1adb8; 75 | --sidebar-non-existant: #505254; 76 | --sidebar-active: #3473ad; 77 | --sidebar-spacer: #393939; 78 | 79 | --scrollbar: var(--sidebar-fg); 80 | 81 | --icons: #43484d; 82 | --icons-hover: #b3c0cc; 83 | 84 | --links: #2b79a2; 85 | 86 | --inline-code-color: #c5c8c6; 87 | 88 | --theme-popup-bg: #141617; 89 | --theme-popup-border: #43484d; 90 | --theme-hover: #1f2124; 91 | 92 | --quote-bg: hsl(234, 21%, 18%); 93 | --quote-border: hsl(234, 21%, 23%); 94 | 95 | --warning-border: #ff8e00; 96 | 97 | --table-border-color: hsl(200, 7%, 13%); 98 | --table-header-bg: hsl(200, 7%, 28%); 99 | --table-alternate-bg: hsl(200, 7%, 11%); 100 | 101 | --searchbar-border-color: #aaa; 102 | --searchbar-bg: #b7b7b7; 103 | --searchbar-fg: #000; 104 | --searchbar-shadow-color: #aaa; 105 | --searchresults-header-fg: #666; 106 | --searchresults-border-color: #98a3ad; 107 | --searchresults-li-bg: #2b2b2f; 108 | --search-mark-bg: #355c7d; 109 | 110 | --color-scheme: dark; 111 | } 112 | 113 | .light { 114 | --bg: hsl(0, 0%, 100%); 115 | --fg: hsl(0, 0%, 0%); 116 | 117 | --sidebar-bg: #fafafa; 118 | --sidebar-fg: hsl(0, 0%, 0%); 119 | --sidebar-non-existant: #aaaaaa; 120 | --sidebar-active: #1f1fff; 121 | --sidebar-spacer: #f4f4f4; 122 | 123 | --scrollbar: #8F8F8F; 124 | 125 | --icons: #747474; 126 | --icons-hover: #000000; 127 | 128 | --links: #1f1fff; 129 | 130 | --inline-code-color: #F42C4C; 131 | 132 | --theme-popup-bg: #fafafa; 133 | --theme-popup-border: #cccccc; 134 | --theme-hover: #e6e6e6; 135 | 136 | --quote-bg: hsl(197, 37%, 96%); 137 | --quote-border: hsl(197, 37%, 91%); 138 | 139 | --warning-border: #ff8e00; 140 | 141 | --table-border-color: hsl(0, 0%, 95%); 142 | --table-header-bg: hsl(0, 0%, 80%); 143 | --table-alternate-bg: hsl(0, 0%, 97%); 144 | 145 | --searchbar-border-color: #aaa; 146 | --searchbar-bg: #fafafa; 147 | --searchbar-fg: #000; 148 | --searchbar-shadow-color: #aaa; 149 | --searchresults-header-fg: #666; 150 | --searchresults-border-color: #888; 151 | --searchresults-li-bg: #e4f2fe; 152 | --search-mark-bg: #a2cff5; 153 | 154 | --color-scheme: light; 155 | } 156 | 157 | .navy { 158 | --bg: hsl(226, 23%, 11%); 159 | --fg: #bcbdd0; 160 | 161 | --sidebar-bg: #282d3f; 162 | --sidebar-fg: #c8c9db; 163 | --sidebar-non-existant: #505274; 164 | --sidebar-active: #2b79a2; 165 | --sidebar-spacer: #2d334f; 166 | 167 | --scrollbar: var(--sidebar-fg); 168 | 169 | --icons: #737480; 170 | --icons-hover: #b7b9cc; 171 | 172 | --links: #2b79a2; 173 | 174 | --inline-code-color: #c5c8c6; 175 | 176 | --theme-popup-bg: #161923; 177 | --theme-popup-border: #737480; 178 | --theme-hover: #282e40; 179 | 180 | --quote-bg: hsl(226, 15%, 17%); 181 | --quote-border: hsl(226, 15%, 22%); 182 | 183 | --warning-border: #ff8e00; 184 | 185 | --table-border-color: hsl(226, 23%, 16%); 186 | --table-header-bg: hsl(226, 23%, 31%); 187 | --table-alternate-bg: hsl(226, 23%, 14%); 188 | 189 | --searchbar-border-color: #aaa; 190 | --searchbar-bg: #aeaec6; 191 | --searchbar-fg: #000; 192 | --searchbar-shadow-color: #aaa; 193 | --searchresults-header-fg: #5f5f71; 194 | --searchresults-border-color: #5c5c68; 195 | --searchresults-li-bg: #242430; 196 | --search-mark-bg: #a2cff5; 197 | 198 | --color-scheme: dark; 199 | } 200 | 201 | .rust { 202 | --bg: hsl(60, 9%, 87%); 203 | --fg: #262625; 204 | 205 | --sidebar-bg: #3b2e2a; 206 | --sidebar-fg: #c8c9db; 207 | --sidebar-non-existant: #505254; 208 | --sidebar-active: #e69f67; 209 | --sidebar-spacer: #45373a; 210 | 211 | --scrollbar: var(--sidebar-fg); 212 | 213 | --icons: #737480; 214 | --icons-hover: #262625; 215 | 216 | --links: #2b79a2; 217 | 218 | --inline-code-color: #6e6b5e; 219 | 220 | --theme-popup-bg: #e1e1db; 221 | --theme-popup-border: #b38f6b; 222 | --theme-hover: #99908a; 223 | 224 | --quote-bg: hsl(60, 5%, 75%); 225 | --quote-border: hsl(60, 5%, 70%); 226 | 227 | --warning-border: #ff8e00; 228 | 229 | --table-border-color: hsl(60, 9%, 82%); 230 | --table-header-bg: #b3a497; 231 | --table-alternate-bg: hsl(60, 9%, 84%); 232 | 233 | --searchbar-border-color: #aaa; 234 | --searchbar-bg: #fafafa; 235 | --searchbar-fg: #000; 236 | --searchbar-shadow-color: #aaa; 237 | --searchresults-header-fg: #666; 238 | --searchresults-border-color: #888; 239 | --searchresults-li-bg: #dec2a2; 240 | --search-mark-bg: #e69f67; 241 | 242 | --color-scheme: light; 243 | } 244 | 245 | @media (prefers-color-scheme: dark) { 246 | .light.no-js { 247 | --bg: hsl(200, 7%, 8%); 248 | --fg: #98a3ad; 249 | 250 | --sidebar-bg: #292c2f; 251 | --sidebar-fg: #a1adb8; 252 | --sidebar-non-existant: #505254; 253 | --sidebar-active: #3473ad; 254 | --sidebar-spacer: #393939; 255 | 256 | --scrollbar: var(--sidebar-fg); 257 | 258 | --icons: #43484d; 259 | --icons-hover: #b3c0cc; 260 | 261 | --links: #2b79a2; 262 | 263 | --inline-code-color: #c5c8c6; 264 | 265 | --theme-popup-bg: #141617; 266 | --theme-popup-border: #43484d; 267 | --theme-hover: #1f2124; 268 | 269 | --quote-bg: hsl(234, 21%, 18%); 270 | --quote-border: hsl(234, 21%, 23%); 271 | 272 | --warning-border: #ff8e00; 273 | 274 | --table-border-color: hsl(200, 7%, 13%); 275 | --table-header-bg: hsl(200, 7%, 28%); 276 | --table-alternate-bg: hsl(200, 7%, 11%); 277 | 278 | --searchbar-border-color: #aaa; 279 | --searchbar-bg: #b7b7b7; 280 | --searchbar-fg: #000; 281 | --searchbar-shadow-color: #aaa; 282 | --searchresults-header-fg: #666; 283 | --searchresults-border-color: #98a3ad; 284 | --searchresults-li-bg: #2b2b2f; 285 | --search-mark-bg: #355c7d; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /theme/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | {{#if is_print }} 8 | 9 | {{/if}} 10 | {{#if base_url}} 11 | 12 | {{/if}} 13 | 14 | 15 | 16 | {{> head}} 17 | 18 | 19 | 20 | 21 | 22 | {{#if favicon_svg}} 23 | 24 | {{/if}} 25 | {{#if favicon_png}} 26 | 27 | {{/if}} 28 | 29 | 30 | 31 | {{#if print_enable}} 32 | 33 | {{/if}} 34 | 35 | 36 | 37 | {{#if copy_fonts}} 38 | 39 | {{/if}} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{#each additional_css}} 48 | 49 | {{/each}} 50 | 51 | {{#if mathjax_support}} 52 | 53 | 54 | {{/if}} 55 | 56 | 57 |
58 | 59 | 63 | 64 | 65 | 79 | 80 | 81 | 92 | 93 | 94 | 95 | 96 | 110 | 111 | 119 | 120 | 121 | 141 | 142 |
143 | 144 |
145 | {{> header}} 146 | 147 | 190 | 191 | {{#if search_enabled}} 192 | 202 | {{/if}} 203 | 204 | 205 | 212 | 213 |
214 |
215 | 216 |
217 | 218 | {{{ content }}} 219 |
220 | 221 | 237 |
238 |
239 | 240 | 253 | 254 |
255 | 256 | {{#if live_reload_endpoint}} 257 | 258 | 273 | {{/if}} 274 | 275 | {{#if google_analytics}} 276 | 277 | 292 | {{/if}} 293 | 294 | {{#if playground_line_numbers}} 295 | 298 | {{/if}} 299 | 300 | {{#if playground_copyable}} 301 | 304 | {{/if}} 305 | 306 | {{#if playground_js}} 307 | 308 | 309 | 310 | 311 | 312 | {{/if}} 313 | 314 | {{#if search_js}} 315 | 316 | 317 | 318 | {{/if}} 319 | 320 | 321 | 322 | 323 | 324 | 325 | {{#each additional_js}} 326 | 327 | {{/each}} 328 | 329 | {{#if is_print}} 330 | {{#if mathjax_support}} 331 | 338 | {{else}} 339 | 344 | {{/if}} 345 | {{/if}} 346 | 347 |
348 | 349 | 350 | -------------------------------------------------------------------------------- /theme/pagetoc.css: -------------------------------------------------------------------------------- 1 | /* src: https://github.com/JorelAli/mdBook-pagetoc */ 2 | 3 | @media only screen and (max-width:1439px) { 4 | .sidetoc { 5 | display: none; 6 | } 7 | } 8 | 9 | @media only screen and (min-width:1440px) { 10 | main { 11 | position: relative; 12 | } 13 | .sidetoc { 14 | margin-left: auto; 15 | margin-right: auto; 16 | /* left: calc(90% + (var(--content-min-width))/4 - 110px); */ 17 | left: 101%; 18 | position: absolute; 19 | font-size: var(--pagetoc-fontsize); 20 | } 21 | .pagetoc { 22 | position: fixed; 23 | width: var(--pagetoc-width); 24 | } 25 | .pagetoc a { 26 | border-left: 1px solid var(--sidebar-bg); 27 | /* color: var(--fg); */ 28 | /* color: var(--sidebar-fg); */ 29 | color: var(--links); 30 | display: block; 31 | padding-bottom: 5px; 32 | padding-top: 5px; 33 | padding-left: 10px; 34 | text-align: left; 35 | text-decoration: none; 36 | font-weight: normal; 37 | background: var(--sidebar-bg); 38 | } 39 | .pagetoc a:hover, 40 | .pagetoc a.active { 41 | background: var(--sidebar-bg); 42 | /* color: var(--sidebar-fg); */ 43 | color: var(--sidebar-active); 44 | font-weight: bold; 45 | font-size: var(--pagetoc-fontsize); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /theme/pagetoc.js: -------------------------------------------------------------------------------- 1 | // src: https://github.com/JorelAli/mdBook-pagetoc 2 | 3 | // Un-active everything when you click it 4 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { 5 | el.addEventHandler("click", function() { 6 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { 7 | el.classList.remove("active"); 8 | }); 9 | el.classList.add("active"); 10 | }); 11 | }); 12 | 13 | var updateFunction = function() { 14 | 15 | var id; 16 | var elements = document.getElementsByClassName("header"); 17 | Array.prototype.forEach.call(elements, function(el, i) { 18 | if (window.pageYOffset >= el.offsetTop) { 19 | id = el; 20 | } 21 | }); 22 | 23 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { 24 | el.classList.remove("active"); 25 | }); 26 | 27 | Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function(el, i) { 28 | if (id.href.localeCompare(el.href) == 0) { 29 | el.classList.add("active"); 30 | } 31 | }); 32 | }; 33 | 34 | // Populate sidebar on load 35 | window.addEventListener('load', function() { 36 | var pagetoc = document.getElementsByClassName("pagetoc")[0]; 37 | var elements = document.getElementsByClassName("header"); 38 | Array.prototype.forEach.call(elements, function(el, i) { 39 | var link = document.createElement("a"); 40 | 41 | // Indent shows hierarchy 42 | var indent = ""; 43 | switch (el.parentElement.tagName) { 44 | case "H2": 45 | indent = "20px"; 46 | break; 47 | case "H3": 48 | indent = "40px"; 49 | break; 50 | case "H4": 51 | indent = "60px"; 52 | break; 53 | default: 54 | break; 55 | } 56 | 57 | link.appendChild(document.createTextNode(el.text)); 58 | link.style.paddingLeft = indent; 59 | link.href = el.href; 60 | pagetoc.appendChild(link); 61 | }); 62 | updateFunction.call(); 63 | }); 64 | 65 | 66 | 67 | // Handle active elements on scroll 68 | window.addEventListener("scroll", updateFunction); 69 | -------------------------------------------------------------------------------- /theme/rust-syntax-bg-highlight.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT 3 | * file at the top-level directory of this distribution and at 4 | * http://rust-lang.org/COPYRIGHT. 5 | * With elements taken from Bootstrap v3.0.2 (MIT licensed). 6 | * 7 | * Licensed under the Apache License, Version 2.0 or the MIT license 9 | * , at your 10 | * option. This file may not be copied, modified, or distributed 11 | * except according to those terms. 12 | * 13 | * Modified by Daniel Keep. 14 | */ 15 | 16 | 17 | /* Code highlighting */ 18 | pre.rust .kw { color: #f92672; } 19 | pre.rust .kw-2 { color: #f92672; } 20 | pre.rust .prelude-ty { color: #66d9ef; } 21 | pre.rust .number { color: #EFD6AB; } 22 | pre.rust .string { color: #F8E9A5; } 23 | pre.rust .self { color: #f92672; } 24 | pre.rust .boolval { color: #EFD6AB; } 25 | pre.rust .prelude-val { color: #66d9ef; } 26 | pre.rust .attribute { color: #C7AFD1; } 27 | pre.rust .attribute .ident { color: #D4BBDE; } 28 | pre.rust .comment { color: #75715e; } 29 | pre.rust .doccomment { color: #75715e; } 30 | pre.rust .macro { color: #AC7FE7; } 31 | pre.rust .macro-nonterminal { color: #CFC0E2; } 32 | pre.rust .lifetime { color: #B76514; } 33 | pre.rust .op { color: #f92672; } 34 | 35 | pre.rust .synctx-0 { background-color: rgba(255, 0, 0, 0.1); } 36 | pre.rust .synctx-1 { background-color: rgba(0, 255, 0, 0.1); } 37 | --------------------------------------------------------------------------------