├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── book.toml ├── src ├── SUMMARY.md ├── about.md ├── checklist.md ├── debuggability.md ├── dependability.md ├── documentation.md ├── external-links.md ├── flexibility.md ├── future-proofing.md ├── interoperability.md ├── macros.md ├── naming.md ├── necessities.md ├── predictability.md └── type-safety.md └── theme ├── css ├── chrome.css ├── general.css └── variables.css ├── index.hbs ├── pagetoc.css └── pagetoc.js /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup mdBook 15 | uses: peaceiris/actions-mdbook@v1 16 | with: 17 | mdbook-version: 'latest' 18 | 19 | - name: Setup mdbook-theme latest 20 | run: | 21 | curl -s https://api.github.com/repos/zjp-CN/mdbook-theme/releases/latest \ 22 | | grep browser_download_url \ 23 | | grep mdbook-theme_linux \ 24 | | cut -d '"' -f 4 \ 25 | | wget -qi - 26 | tar -xvzf mdbook-theme_linux.tar.gz 27 | echo $PWD >> $GITHUB_PATH 28 | 29 | - run: mdbook build 30 | 31 | - name: Deploy 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: ./book 36 | force_orphan: true 37 | user_name: 'github-actions[bot]' 38 | user_email: 'github-actions[bot]@users.noreply.github.com' 39 | commit_message: ${{ github.event.head_commit.message }} 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /book 2 | /mdbook 3 | /mdbook-*.tar.gz 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api-guidelines"] 2 | path = api-guidelines 3 | url = https://github.com/rust-lang/api-guidelines.git 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 如何贡献 API 编写指南 2 | 3 | Rust API 编写指南 项目欢迎任何人 4 | 以提建议、提交 bug 报告、提 PR 、提出反馈的方式,来贡献自己的力量。 5 | 如果你正在考虑帮助我们,这篇文档给你一些指导。 6 | 7 | 如果可以的话,请到 Github [issue] 或 [Gitter] 频道 联系我们。 8 | 9 | [issue]: https://github.com/rust-lang/api-guidelines/issues 10 | [Gitter]: https://gitter.im/rust-impl-period/WG-libs-guidelines 11 | 12 | ## 提交新指南 13 | 14 | 我们一直寻找来自高质量的 Rust 库的编写经验。 15 | 如果你对 crate API 有洞见,想让其他 crates 从中受益, 16 | 请开启一个 [讨论][open a discussion] 让我们知道。 17 | 18 | 如果你想对现有的指南进行具体的修正, 19 | 也可以提交一个 [issue][open a issue] 。 20 | 21 | [open a discussion]: https://github.com/rust-lang/api-guidelines/discussions/new 22 | [open an issue]: https://github.com/rust-lang/api-guidelines/issues/new 23 | 24 | ## 编写指南内容 25 | 26 | 指南以一系列 Markdown 文件的形式放置在 [`src`] 目录。 27 | 当做出某些修改的时候,你可以使用 [mdBook] 来预览渲染的内容。 28 | 29 | [`src`]: https://github.com/rust-lang/api-guidelines/tree/master/src 30 | [mdBook]: https://github.com/azerupi/mdBook 31 | 32 | ```sh 33 | cargo install mdbook 34 | 35 | # 在 api-guidelines 目录 36 | mdbook serve 37 | ``` 38 | 39 | 使用 `mdbook serve` 命令可以让渲染的 API 编写指南一直在 40 | http://localhost:3000/ 页面可浏览。 41 | 42 | ## 对编写指南的建议 43 | 44 | 我们遵循一些语法方面的规则来确保指南的一览表风格保持一致和内容清晰可读。 45 | 46 | 一条指南是对假想 crate 的 **陈述说明** 。 47 | 48 | ```diff 49 | 不要用祈使语气: 50 | - "Implement Hex, Octal, Binary for binary number types" 51 | 使用陈述语气: 52 | + "Binary number types provide Hex, Octal, Binary formatting" 53 | 54 | 不要使用命令: 55 | - "Macros should compose well with attributes" 56 | 请使用陈述: 57 | + "Macros compose well with attributes" 58 | ``` 59 | 60 | 指南具有明显的 **主语** 和 **动词** 。 61 | 62 | ```diff 63 | 不要无主语: 64 | - "Includes all common Cargo.toml metadata" 65 | 请添加主语: 66 | + "Cargo.toml includes all common metadata" 67 | 68 | 不要无动词: 69 | - "Thoroughly documented with examples" 70 | 请添加动词: 71 | + "Crate level docs are thorough and include examples" 72 | 73 | 不要用模糊的句式: 74 | - "There are no out-parameters" 75 | 请使用具体的句式: 76 | + "Functions do not take out-parameters" 77 | ``` 78 | 79 | 指南使用 **主动语态** 。 80 | 81 | ```diff 82 | 不要用被动语态: 83 | - "Function arguments are validated" 84 | 请使用主动语态: 85 | + "Functions validate their arguments" 86 | ``` 87 | 88 | ## 规范 89 | 90 | 在所有与 Rust 有关的领域,我们都遵循 [Rust 代码准则][Rust Code of Conduct] 。 91 | 与这方面有关的问题,请联系 Rust moderation 团队: rust-mods@rust-lang.org 。 92 | 93 | [Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct 94 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust API 编写指南 2 | 3 | 这是一组关于如何设计和呈现 Rust APIs 的建议。 4 | 这些建议主要由 Rust library 团队编写, 5 | 总结了 Rust 生态下构建标准库和其他 crates 的经验。 6 | 7 | 阅读地址: 8 | [原版](https://rust-lang.github.io/api-guidelines) | 9 | [中文版](https://zjp-cn.github.io/api-guidelines) | 10 | [国内站点](http://129.28.186.100/api-guidelines) 11 | 12 | ## 加入讨论 13 | 14 | 查看 [讨论](https://github.com/rust-lang/api-guidelines/discussions) 15 | 页面来提出新的 API 编写指南、对如何使用这些指南进行提问。 16 | 17 | ## 版权 18 | 19 | 这个项目具有 20 | [Apache License, Version 2.0](LICENSE-APACHE) 或 21 | [MIT license](LICENSE-MIT) 22 | 双重许可,供你选择。 23 | 24 | ## 贡献 25 | 26 | 除非你明确声明,否则您有意提交的任何包含在本项目中的贡献, 27 | 正如 Apache 2.0 许可证中所定义,应具有上述双重许可,无任何附加条款或条件。 28 | 29 | 查看 [贡献说明](CONTRIBUTING.md) 。 30 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Rust API Guidelines(Rust API 编写指南)" 3 | description = "关于如何设计和呈现 Rust APIs 的一些建议" 4 | language = "zh" 5 | 6 | [preprocessor.theme] 7 | pagetoc = true 8 | turn-off = true 9 | root-font-size = "70%" 10 | sidebar-width = "120px" 11 | pagetoc-width = "15%" 12 | content-max-width = "80%" 13 | 14 | [output.html] 15 | git-repository-url = "https://github.com/zjp-CN/api-guidelines" 16 | additional-css = ["theme/pagetoc.css"] 17 | additional-js = ["theme/pagetoc.js"] 18 | 19 | [output.html.fold] 20 | enable = true 21 | level = 0 22 | 23 | [output.html.playground] 24 | editable = true 25 | 26 | [output.html.print] 27 | enable = false 28 | 29 | [output.theme-ace] 30 | theme-white = "github" 31 | theme-dark = "solarized_dark" 32 | below-build-dir = true 33 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [关于本书](about.md) 4 | [一览表](checklist.md) 5 | 6 | - [命名](naming.md) 7 | - [互通互用](interoperability.md) 8 | - [宏](macros.md) 9 | - [文档编写](documentation.md) 10 | - [可预测](predictability.md) 11 | - [灵活性](flexibility.md) 12 | - [类型安全](type-safety.md) 13 | - [可依赖](dependability.md) 14 | - [可调试](debuggability.md) 15 | - [前瞻性](future-proofing.md) 16 | - [必要项](necessities.md) 17 | 18 | [拓展阅读](external-links.md) 19 | -------------------------------------------------------------------------------- /src/about.md: -------------------------------------------------------------------------------- 1 | # Rust API 编写指南 2 | 3 | 这是一组关于如何设计和呈现 Rust APIs 的建议。 4 | 这些建议主要由 Rust library 团队编写, 5 | 总结了 Rust 生态下构建标准库和其他 crates 的经验。 6 | 7 | 这些建议只是给你指导。 8 | 有些指导建议相比而言需要严格遵守。 9 | 有些不那么严格,因为那些是模糊的、仍然在发展完善中的。 10 | 11 | Rust crate 的编写者应该站在符合 Rust 语言习惯、且相互协同的开发角度, 12 | 慎重考虑和采纳他们认为合适的建议。\ 13 | 虽然 crate 编写者可能觉得 **采纳** 这些建议的 crates 比那些 14 | **不采纳** 这些建议的 crates 会更好地融入现有的 crate 生态, 15 | 但是这些指导建议不应该被认为是 crate 作者所必须遵守的。 16 | 17 | 这本书分为两部分: 18 | 1. 简明的 [一览表][checklist] :罗列了所有单独的原则[^guidelines], 19 | 适合在检查修改 crate 时进行浏览。 20 | 2. 专题章节 (topical chapters) 详细解释了这些原则。 21 | 22 | 如果你对贡献 API 编写指南感兴趣,可以看看 [贡献说明][contributing.md]、 23 | 加入我们的 [Gitter 频道][Gitter channel] 。[^project] 24 | 25 | [checklist]: checklist.html 26 | [contributing.md]: https://github.com/rust-lang/api-guidelines/blob/master/CONTRIBUTING.md 27 | [Gitter channel]: https://gitter.im/rust-impl-period/WG-libs-guidelines 28 | [^guidelines]: 译者注:鉴于这本书的 *guidelines* 是 “**建议**” 而非 “必须” , 29 | 所以翻译成 “原则” 语义偏重,但是译者暂时没想到此处有比 “原则” 更通顺而精简的词语。\ 30 | 此外,为了语句通顺, *guidelines* 在不同的语境下会翻译成 “指南” 、 “编写指南” 、 31 | “指导” 、 “指导建议” 、 “原则” 之类的同义词。 32 | 33 | [^project]: 译者注: 34 | [原项目地址](https://github.com/rust-lang/api-guidelines) | 35 | [中文渲染版](https://zjp-cn.github.io/api-guidelines) | 36 | [国内站点](http://129.28.186.100/api-guidelines) 37 | 38 | -------------------------------------------------------------------------------- /src/checklist.md: -------------------------------------------------------------------------------- 1 | # Rust API 指导清单 2 | 3 | 4 | 5 | - **命名** ( *crate 遵照 Rust 命名规范* ) 6 | - 大小写规范 RFC 430 ([C-CASE]) 7 | - 遵循 `as_`, `to_`, `into_` 规范 用以特定类型转换 ([C-CONV]) 8 | - getter 命名规范 ([C-GETTER]) 9 | - 遵循 `iter`, `iter_mut`, `into_iter` 规范 用以生成迭代器 ([C-ITER]) 10 | - 生成迭代器的方法与迭代器类型同名 ([C-ITER-TY]) 11 | - cargo feature 名中不应该有无意义的词 ([C-FEATURE]) 12 | - 词性顺序一致 ([C-WORD-ORDER]) 13 | - **互通互用** ( *crate 很好地与其他库提供的功能进行交互* ) 14 | - 类型应尽早实现常见的 traits ([C-COMMON-TRAITS]) 15 | - `Copy`, `Clone`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, `Hash`, `Debug`, 16 | `Display`, `Default` 17 | - 使用 `From`, `AsRef`, `AsMut` trait 来转换类型 ([C-CONV-TRAITS]) 18 | - 给集合实现 `FromIterator` 和 `Extend` trait ([C-COLLECT]) 19 | - 给数据结构实现 Serde 的 `Serialize` 和 `Deserialize` trait ([C-SERDE]) 20 | - 类型应尽可能实现 `Send` 和 `Sync` trait ([C-SEND-SYNC]) 21 | - Error 类型 十分直观和有用 ([C-GOOD-ERR]) 22 | - 二进制数类型应提供 `Hex`, `Octal`, `Binary` 的格式化方式 ([C-NUM-FMT]) 23 | - reader/writer 泛型函数使用 `R: Read` 和 `W: Write` 参数传值 ([C-RW-VALUE]) 24 | - **宏** ( *crate 应展现良好的宏* ) 25 | - 输入语法与输出语法一致 ([C-EVOCATIVE]) 26 | - 宏与属性形成有机的整体 ([C-MACRO-ATTR]) 27 | - 生成条目的宏可以在条目被允许的地方使用 ([C-ANYWHERE]) 28 | - 生成条目的宏应支持可见性分类符 ([C-MACRO-VIS]) 29 | - 类型分类符 `$t:ty` 是灵活的 ([C-MACRO-TY]) 30 | - **文档编写** ( *crate 有丰富的文档说明* ) 31 | - crate 级别的文档应该详实有例 ([C-CRATE-DOC]) 32 | - 每个条目都应该有例子 ([C-EXAMPLE]) 33 | - 例子应该使用 `?` 而不使用 `try!` 或者 `unwrap` ([C-QUESTION-MARK]) 34 | - 函数涉及错误、 panic、安全性时 应该加以说明 ([C-FAILURE]) 35 | - 给相关的内容添加超链接 ([C-LINK]) 36 | - Cargo.toml 应包含所有常见的配置数据 ([C-METADATA]) 37 | - 作者、描述、版权、主页、文档、仓库、readme、关键词、分类 38 | - 设置 html_root_url 属性 "https://docs.rs/CRATE/X.Y.Z" ([C-HTML-ROOT]) 39 | - 发布时 记录该版本的重大变化 ([C-RELNOTES]) 40 | - 文档不应该展示无太大帮助的实现细节 ([C-HIDDEN]) 41 | - **可预测** ( *crate 让清晰可读的代码正如它展示的那样工作* ) 42 | - 智能指针不增加固有方法 ([C-SMART-PTR]) 43 | - 类型转换的重点应放在涉及类型中最明确的类型上 ([C-CONV-SPECIFIC]) 44 | - 有清楚接收者的函数应写成方法的形式 ([C-METHOD]) 45 | - 函数不该把返回值作为其参数 ([C-NO-OUT]) 46 | - 重载运算符不足为奇 ([C-OVERLOAD]) 47 | - 只对智能指针实现 `Deref` 和 `DerefMut` trait ([C-DEREF]) 48 | - 构造函数是静态的、固有的方法 ([C-CTOR]) 49 | - **灵活性** ( *crate 应支持现实中各种各样的使用场景* ) 50 | - 为避免重复计算 函数应提供中间结果 ([C-INTERMEDIATE]) 51 | - 调用方决定在何处复制和替换数据 ([C-CALLER-CONTROL]) 52 | - 函数通过泛型来对参数做最小范围的假设 ([C-GENERIC]) 53 | - trait 用作 object 时应当是安全的 ([C-OBJECT]) 54 | - **类型安全** ( *crate 应有效地利用类型系统* ) 55 | - newtype 提供静态的区分功能 ([C-NEWTYPE]) 56 | - 参数应使用类型来表明意图 ([C-CUSTOM-TYPE]) 57 | - 用 `bitflags` 来存放一组标志 ([C-BITFLAG]) 58 | - 利用构造模式构造复杂的值 ([C-BUILDER]) 59 | - **可依赖** ( *crate 不太可能出错* ) 60 | - 函数会验证其参数 ([C-VALIDATE]) 61 | - 析构函数不该运行失败 ([C-DTOR-FAIL]) 62 | - 阻塞的析构函数应有替代的办法 ([C-DTOR-BLOCK]) 63 | - **可调试** ( *crate 易于调试* ) 64 | - 所有公有的类型都应该实现 `Debug` ([C-DEBUG]) 65 | - `Debug` 呈现的内容永远不为空 ([C-DEBUG-NONEMPTY]) 66 | - **前瞻性** ( *crate 能在不破坏使用者代码的情况下随时改进* ) 67 | - 封装的 traits 隔绝下游的实现 ([C-SEALED]) 68 | - 结构体具有私有字段 ([C-STRUCT-PRIVATE]) 69 | - newtypes 封装起实现的细节 ([C-NEWTYPE-HIDE]) 70 | - 已经 `derive` 的数据结构不应该再使用 trait bounds ([C-STRUCT-BOUNDS]) 71 | - **必要项** ( *对于使用者来说,这真的很重要* ) 72 | - 稳定版 crate 必须具有稳定的公有依赖 ([C-STABLE]) 73 | - crate 及其依赖必须有许可证 ([C-PERMISSIVE]) 74 | 75 | 76 | [C-CASE]: naming.html#c-case 77 | [C-CONV]: naming.html#c-conv 78 | [C-GETTER]: naming.html#c-getter 79 | [C-ITER]: naming.html#c-iter 80 | [C-ITER-TY]: naming.html#c-iter-ty 81 | [C-FEATURE]: naming.html#c-feature 82 | [C-WORD-ORDER]: naming.html#c-word-order 83 | 84 | [C-COMMON-TRAITS]: interoperability.html#c-common-traits 85 | [C-CONV-TRAITS]: interoperability.html#c-conv-traits 86 | [C-COLLECT]: interoperability.html#c-collect 87 | [C-SERDE]: interoperability.html#c-serde 88 | [C-SEND-SYNC]: interoperability.html#c-send-sync 89 | [C-GOOD-ERR]: interoperability.html#c-good-err 90 | [C-NUM-FMT]: interoperability.html#c-num-fmt 91 | [C-RW-VALUE]: interoperability.html#c-rw-value 92 | 93 | [C-EVOCATIVE]: macros.html#c-evocative 94 | [C-MACRO-ATTR]: macros.html#c-macro-attr 95 | [C-ANYWHERE]: macros.html#c-anywhere 96 | [C-MACRO-VIS]: macros.html#c-macro-vis 97 | [C-MACRO-TY]: macros.html#c-macro-ty 98 | 99 | [C-CRATE-DOC]: documentation.html#c-crate-doc 100 | [C-EXAMPLE]: documentation.html#c-example 101 | [C-QUESTION-MARK]: documentation.html#c-question-mark 102 | [C-FAILURE]: documentation.html#c-failure 103 | [C-LINK]: documentation.html#c-link 104 | [C-METADATA]: documentation.html#c-metadata 105 | [C-HTML-ROOT]: documentation.html#c-html-root 106 | [C-RELNOTES]: documentation.html#c-relnotes 107 | [C-HIDDEN]: documentation.html#c-hidden 108 | 109 | [C-SMART-PTR]: predictability.html#c-smart-ptr 110 | [C-CONV-SPECIFIC]: predictability.html#c-conv-specific 111 | [C-METHOD]: predictability.html#c-method 112 | [C-NO-OUT]: predictability.html#c-no-out 113 | [C-OVERLOAD]: predictability.html#c-overload 114 | [C-DEREF]: predictability.html#c-deref 115 | [C-CTOR]: predictability.html#c-ctor 116 | 117 | [C-INTERMEDIATE]: flexibility.html#c-intermediate 118 | [C-CALLER-CONTROL]: flexibility.html#c-caller-control 119 | [C-GENERIC]: flexibility.html#c-generic 120 | [C-OBJECT]: flexibility.html#c-object 121 | 122 | [C-NEWTYPE]: type-safety.html#c-newtype 123 | [C-CUSTOM-TYPE]: type-safety.html#c-custom-type 124 | [C-BITFLAG]: type-safety.html#c-bitflag 125 | [C-BUILDER]: type-safety.html#c-builder 126 | 127 | [C-VALIDATE]: dependability.html#c-validate 128 | [C-DTOR-FAIL]: dependability.html#c-dtor-fail 129 | [C-DTOR-BLOCK]: dependability.html#c-dtor-block 130 | 131 | [C-DEBUG]: debuggability.html#c-debug 132 | [C-DEBUG-NONEMPTY]: debuggability.html#c-debug-nonempty 133 | 134 | [C-SEALED]: future-proofing.html#c-sealed 135 | [C-STRUCT-PRIVATE]: future-proofing.html#c-struct-private 136 | [C-NEWTYPE-HIDE]: future-proofing.html#c-newtype-hide 137 | [C-STRUCT-BOUNDS]: future-proofing.html#c-struct-bounds 138 | 139 | [C-STABLE]: necessities.html#c-stable 140 | [C-PERMISSIVE]: necessities.html#c-permissive 141 | -------------------------------------------------------------------------------- /src/debuggability.md: -------------------------------------------------------------------------------- 1 | # 可调试 2 | 3 | 4 | 5 | ## 所有公有的类型都应该实现 `Debug` 6 | 7 | > All public types implement `Debug` (C-DEBUG) 8 | 9 | 这条原则几乎没有例外。 10 | 11 | 12 | 13 | ## `Debug` 呈现的内容永远不为空 14 | 15 | > `Debug` representation is never empty (C-DEBUG-NONEMPTY) 16 | 17 | 即使是概念上为空的值,其 `Debug` 呈现的内容也永远不应该是空着的。 18 | 19 | ```rust 20 | let empty_str = ""; 21 | assert_eq!(format!("{:?}", empty_str), "\"\""); 22 | 23 | let empty_vec = Vec::::new(); 24 | assert_eq!(format!("{:?}", empty_vec), "[]"); 25 | ``` 26 | -------------------------------------------------------------------------------- /src/dependability.md: -------------------------------------------------------------------------------- 1 | # 可依赖 2 | 3 | 4 | 5 | ## 函数会验证其参数 6 | 7 | > Functions validate their arguments (C-VALIDATE) 8 | 9 | Rust APIs 不遵循 [稳健性法则][robustness principle]: 10 | 对己方保守,对他方宽容。 11 | 12 | [robustness principle]: http://en.wikipedia.org/wiki/Robustness_principle 13 | 14 | 正相反, Rust 代码应该在任何可以 **强制** 校验其输入的时候进行强制校验。 15 | 16 | 可以通过以下机制来实现强制验证。出现的顺序代表性能高低。 17 | 18 | ### 静态验证 19 | 20 | 把输入限制在类型上,排除掉不符合的输入。 21 | 比如,请这样写: 22 | 23 | 24 | ```rust,ignored 25 | fn foo(a: Ascii) { /* ... */ } 26 | ``` 27 | 28 | 而不是这样写: 29 | 30 | ```rust,ignored 31 | fn foo(a: u8) { /* ... */ } 32 | ``` 33 | 34 | `Ascii` 是对 `u8` 类型的包装器 (wrapper) ,从而保证最高位是零。 35 | 参考 [newtype][C-NEWTYPE] 模式,来详细了解生成类型安全的包装器。 36 | 37 | 静态验证 (static enforcement) 通常具有很少的运行时代价: 38 | 它的代价在于边界,例如在 `u8` 首先转换为 `Ascii` 类型的时候。 39 | 它也能在早期发现 bugs ,在编译期发现,而不是在运行失败的时候才发现。 40 | 41 | 可是,有些性质很难或者不可能用类型来描述。 42 | 43 | [C-NEWTYPE]: type-safety.html#c-newtype 44 | 45 | ### 动态验证 46 | 47 | 动态验证 (dynamic enforcement) 即 在处理的时候(如果有必要的话处理之前) 校验输入。 48 | 动态检查通常比静态检查更容易实现,但是有以下几个缺点: 49 | 50 | - 运行时开销(除非动态检查是处理输入的一部分) 51 | - bugs 检测不及时 52 | - 带来失败的情况,用户的代码需要通过 `panic!` ,或者 `Result` / `Option` 53 | 类型处理。 54 | 55 | 1. 使用 [`debug_assert!`] 。 56 | 在构建生产版本的时候可以关掉高花销的检查。 57 | 58 | 2. 增加不进行检查的同类函数。 59 | 通常用 `_unchecked` 后缀来标记这个函数不进行某些检查, 60 | 或者把函数放在 `raw` 的子模块下面。\ 61 | 在下面两个场景下使用 unchecked 函数是明智的: 62 | 为了更高的性能;使用者确信输入的数据是有效的。 63 | 64 | [`debug_assert!`]: http://129.28.186.100/rust-docs/rust/html/std/macro.debug_assert.html 65 | 66 | 67 | ## 析构函数不该运行失败 68 | 69 | > Destructors never fail (C-DTOR-FAIL) 70 | 71 | 析构函数 (destructor) 在某项任务失败的时候也会执行, 72 | 如果析构函数运行失败的话程序就会中断且退出。 73 | 74 | 所以为了不让析构函数失败,应该单独提供用于检查的方法。 75 | 比如 `close` 方法就返回 `Result` 来表明遇到了问题。 76 | 77 | 78 | 79 | ## 阻塞的析构函数应有替代的办法 80 | 81 | > Destructors that may block have alternatives (C-DTOR-BLOCK) 82 | 83 | 同样,析构函数不应该调用造成阻塞的操作, 84 | 阻塞会让调试更困难。 85 | 86 | 重申一遍,考虑为不会失败且非阻塞的析构提供单独的方法。 87 | 88 | [destructor]: https://doc.rust-lang.org/nightly/reference/destructors.html 89 | -------------------------------------------------------------------------------- /src/documentation.md: -------------------------------------------------------------------------------- 1 | # 文档编写 2 | 3 | 4 | 5 | ## crate 级别的文档应该详实有例 6 | 7 | > Crate level docs are thorough and include examples (C-CRATE-DOC) 8 | 9 | 参考 [RFC 1687] 。 10 | 11 | [RFC 1687]: https://github.com/rust-lang/rfcs/pull/1687 12 | 13 | 14 | 15 | ## 每个条目都应该有例子 16 | 17 | > All items have a rustdoc example (C-EXAMPLE) 18 | 19 | 每个公有的 模块、 trait 、结构体、枚举体、函数、方法、宏、类型定义 , 20 | 都应该有展示其功能和使用方法的例子。 21 | 22 | 这条原则应该在合理的地方使用。 23 | 24 | 以链接的形式关联另一个条目的使用例子可能就足够了。 25 | 比如一个函数用到一个具体的类型,合适的做法是: 26 | 给这个函数或者类型写一个例子,然后链接这个例子到另一个。 27 | 28 | 写例子的目的不一定仅仅是展示 **如何使用** 这个条目。 29 | 阅读它的人可以理解怎样调用函数、匹配枚举体 和 其他基础的用法。 30 | 但更多时候,例子通常用来展示 **为什么** 使用者愿意去使用这个条目。 31 | 32 | ```rust,ignored 33 | // 这就是一个关于 `.clone()` 的不好的例子。 34 | // 它死板地展示如何调用 clone() ,而完全没有表明 *为什么* 需要这样做。 35 | fn main() { 36 | let hello = "hello"; 37 | 38 | hello.clone(); 39 | } 40 | ``` 41 | 42 | 43 | 44 | ## 例子应该使用 `?` 而不使用 `try!` 或者 `unwrap` 45 | 46 | > Examples use `?`, not `try!`, not `unwrap` (C-QUESTION-MARK) 47 | 48 | 不管你喜不喜欢这样做,例子里的代码通常会被使用者一字不落地复制下来。 49 | 使用者应该慎重地决定怎样处理每个错误。 50 | 51 | 一个容易出错的代码示例通常是按照下面的方式来写: 52 | 由 `cargo test` 编译测试代码块, 53 | 54 | ``` 55 | /// ```rust 56 | /// # use std::error::Error; 57 | /// # 58 | /// # fn main() -> Result<(), Box> { 59 | /// your; 60 | /// example?; 61 | /// code; 62 | /// # 63 | /// # Ok(()) 64 | /// # } 65 | /// ``` 66 | ``` 67 | 68 | 但以 `#` 开头的每行代码不会出现在读者可见的 rustdoc 里。 69 | 70 | ```rust,ignored 71 | # use std::error::Error; 72 | # 73 | # fn main() -> Result<(), Box> { 74 | your; 75 | example?; 76 | code; 77 | # 78 | # Ok(()) 79 | # } 80 | ``` 81 | 82 | 83 | 84 | 85 | ## 函数涉及错误、 panic、安全性时 应该加以说明 86 | 87 | > Function docs include error, panic, and safety considerations (C-FAILURE) 88 | 89 | 引发错误的条件应该在 "Errors" 标题下说明。 90 | 这也适用于 trait 方法 —— 可以或者可能返回错误的方法都应该在 "Errors" 小节进行说明。 91 | 92 | 比如以下标准库里的例子, 93 | [`std::io::Read::read`] trait 里某个方法可能会返回一个错误。 94 | 95 | [`std::io::Read::read`]: https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read 96 | 97 | ``` 98 | /// Pull some bytes from this source into the specified buffer, returning 99 | /// how many bytes were read. 100 | /// 101 | /// ... lots more info ... 102 | /// 103 | /// # Errors 104 | /// 105 | /// If this function encounters any form of I/O or other error, an error 106 | /// variant will be returned. If an error is returned then it must be 107 | /// guaranteed that no bytes were read. 108 | ``` 109 | 110 | 引发 panic 的条件应该在 "Panics" 标题下说明。 111 | 这也适用于 trait 方法 —— 可以或者可能导致 panic 的方法都应该在 "Panics" 小节进行说明。 112 | 113 | 标准库里的 [`Vec::insert`] 方法可能会 panic : 114 | 115 | [`Vec::insert`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.insert 116 | 117 | ``` 118 | /// Inserts an element at position `index` within the vector, shifting all 119 | /// elements after it to the right. 120 | /// 121 | /// # Panics 122 | /// 123 | /// Panics if `index` is out of bounds. 124 | ``` 125 | 126 | 没必要把所有可能想到的 panic 情况都进行说明, 127 | 尤其是如果 panic 发生在调用期间有逻辑错误的时候。 128 | 以如下的方式说明 `Display` 会 panic 就显得多余了。 129 | 130 | ```rust,ignored 131 | /// # Panics 132 | /// 133 | /// This function panics if `T`'s implementation of `Display` panics. 134 | pub fn print(t: T) { 135 | println!("{}", t.to_string()); 136 | } 137 | ``` 138 | 139 | unsafe 的函数应该放在 "Safety" 小节,用来解释如何正确使用这个函数。 140 | 141 | 例如,不安全的 [`std::ptr::read`] 方法必须在以下情况才能被调用: 142 | 143 | [`std::ptr::read`]: https://doc.rust-lang.org/std/ptr/fn.read.html 144 | 145 | ``` 146 | /// Reads the value from `src` without moving it. This leaves the 147 | /// memory in `src` unchanged. 148 | /// 149 | /// # Safety 150 | /// 151 | /// Beyond accepting a raw pointer, this is unsafe because it semantically 152 | /// moves the value out of `src` without preventing further usage of `src`. 153 | /// If `T` is not `Copy`, then care must be taken to ensure that the value at 154 | /// `src` is not used before the data is overwritten again (e.g. with `write`, 155 | /// `zero_memory`, or `copy_memory`). Note that `*src = foo` counts as a use 156 | /// because it will attempt to drop the value previously at `*src`. 157 | /// 158 | /// The pointer must be aligned; use `read_unaligned` if that is not the case. 159 | ``` 160 | 161 | 162 | 163 | ## 给相关的内容添加超链接 164 | 165 | > Prose contains hyperlinks to relevant things (C-LINK) 166 | 167 | 一般使用 markdown 超链接语法 `[text](url)` 来设置超链接。 168 | 链接到某个类型则可以使用 ``[`text`]`` 语法来标注, 169 | 在 docstring 底部另起一行,使用 ``[`text`]: `` 来添加链接地址, 170 | 下面谈谈这里的 `` 。 171 | 172 | 通常这样做来链接到这个类型的方法: 173 | 174 | ```md 175 | [`serialize_struct`]: #method.serialize_struct 176 | ``` 177 | 178 | 通常这样做来链接到其他类型: 179 | 180 | ```md 181 | [`Deserialize`]: trait.Deserialize.html 182 | ``` 183 | 184 | 也可以链接到父模块或者子模块: 185 | 186 | ```md 187 | [`Value`]: ../enum.Value.html 188 | [`DeserializeOwned`]: de/trait.DeserializeOwned.html 189 | ``` 190 | 191 | 这条原则由 RFC 1574 正式推荐,可参考其 ["Link all the things"] 部分。 192 | 193 | ["Link all the things"]: https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md#link-all-the-things 194 | 195 | 196 | 197 | ## Cargo.toml 应包含所有常见的配置数据 198 | 199 | > Cargo.toml includes all common metadata (C-METADATA) 200 | 201 | 在 `Cargo.toml` 的 `[package]` 部分应该要有以下内容: 202 | 203 | - `authors` 204 | - `description` 205 | - `license` 206 | - `repository` 207 | - `readme` 208 | - `keywords` 209 | - `categories` 210 | 211 | 此外,这两个配置字段可选填: 212 | 213 | - `documentation` 214 | - `homepage` 215 | 216 | *crates.io* 默认会把已发布的 crate 文档链接到 [*docs.rs*] 。 217 | `documentation` 配置信息在文档发布到 *docs.rs* 之外的地方时才需要填写。 218 | 比如这个 crate 链接了不在 *docs.rs* 上构建的共享库。 219 | 220 | [*docs.rs*]: https://docs.rs 221 | 222 | `homepage` 配置信息只填写除源码仓库或 API 文档网址之外的单独网站。 223 | 不要让 `homepage` 和 `documentation` 或者 `repository` 的值重复。 224 | 比如 serde 设置其 `homepage` 为 *https://serde.rs* 。 225 | 226 | [C-HTML-ROOT]: #c-html-root 227 | 228 | ## 设置 html_root_url 属性 "https://docs.rs/CRATE/X.Y.Z" 229 | 230 | > Crate sets html_root_url attribute (C-HTML-ROOT) 231 | 232 | 237 | 238 | 加入 crate 使用 docs.rs 作为其主要的 API 文档平台, 239 | 那么 `html_root_url` 应该指向 `"https://docs.rs/CRATE/MAJOR.MINOR.PATCH"` 地址。[^html_root_url] 240 | 241 | 在编译下游 crates 时, `html_root_url` 属性告诉 rustdoc 242 | 如何生成指向条目的 URLs 。 243 | 如果没有这个属性,依赖于这个 crate 的其他 crates 里的文档链接地址就不正确。 244 | 245 | ```rust,ignored 246 | #![doc(html_root_url = "https://docs.rs/log/0.3.8")] 247 | ``` 248 | 249 | 因为这个 URL 包含确定的版本号,所以这个版本号必须和 `Cargo.toml` 里的版本号同步。 250 | [`version-sync`] crate 能帮助你做集成测试,当 `html_root_url` 251 | 里的版本号落后于 crate 的版本号时,测试不通过。 252 | 253 | [`version-sync`]: https://crates.io/crates/version-sync 254 | 255 | 如果你不喜欢使用 [`version-sync`] 提供的测试机制,那么建议你在 `Cargo.toml` 256 | 里 version 一行添加注释来 *提醒你* 保持这两者同步,就像: 257 | 258 | ```toml 259 | version = "0.3.8" # remember to update html_root_url 260 | ``` 261 | 262 | 对于发布在 docs.rs 之外的文档, `html_root_url` 得设置成一个增加 263 | *crate 名 + index.html* 之后能访问到 crate 根目录文档的地址。 264 | 比如 crate 的根目录文档地址是 `"https://api.rocket.rs/rocket/index.html"` , 265 | 那么 `html_root_url` 的值应该填成 `"https://api.rocket.rs"` 。 266 | 267 | [^html_root_url]: 译者注:当 rustdoc 支持不设置 `html_root_url` 默认就指向 docs.rs 268 | 的功能之后,这条原则就可以删除。见 269 | [issue 42301](https://github.com/rust-lang/rust/issues/42301) 。 270 | 此外,你还可以看看文档 [rustdoc: html_root_url] 和 [cargo doc] 。 271 | 272 | [rustdoc: html_root_url]:https://doc.rust-lang.org/rustdoc/the-doc-attribute.html#html_root_url 273 | [cargo doc]: https://doc.rust-lang.org/cargo/commands/cargo-doc.html 274 | 275 | 276 | ## 发布时 记录该版本的重大变化 277 | 278 | > Release notes document all significant changes (C-RELNOTES) 279 | 280 | crate 的使用者能从版本说明 (release note) 中找到每个发布版本的更改概要。 281 | 这些版本说明或者其链接应该在 crate 根文档 以及/或者 282 | Cargo.toml 填写的仓库说明 里有介绍。 283 | 284 | 不兼容的变更 (正如 [RFC 1105] 所定义) 应该清楚地写在版本说明里。 285 | 286 | 如果使用 Git 来追踪 crate 源代码,那么每个发布到 *crates.io* 上的版本 287 | 应该有对应的标签 (tag) 来标记这次已发布的提交记录。 288 | 不使用 Git 的版本控制工具也应该用类似的方式进行处理。 289 | 参考以下 Git 命令: 290 | 291 | ```bash 292 | # Tag the current commit 293 | GIT_COMMITTER_DATE=$(git log -n1 --pretty=%aD) git tag -a -m "Release 0.3.0" 0.3.0 294 | git push --tags 295 | ``` 296 | 297 | 有注释的 tag 会更好,因为一些 Git 命令会在有注释 tag 存在的情况下 298 | 忽略不带注释的 tag 。 299 | 300 | [RFC 1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md 301 | 302 | 例子: 303 | 304 | - [Serde 1.0.0 release notes](https://github.com/serde-rs/serde/releases/tag/v1.0.0) 305 | - [Serde 0.9.8 release notes](https://github.com/serde-rs/serde/releases/tag/v0.9.8) 306 | - [Serde 0.9.0 release notes](https://github.com/serde-rs/serde/releases/tag/v0.9.0) 307 | - [Diesel change log](https://github.com/diesel-rs/diesel/blob/master/CHANGELOG.md) 308 | 309 | 310 | 311 | ## 文档不应该展示无太大帮助的实现细节 312 | 313 | > Rustdoc does not show unhelpful implementation details (C-HIDDEN) 314 | 315 | rustdoc 应该包含帮助使用者充分使用该 crate 的所有信息。 316 | 解释相关的实现细节是可以的, 317 | 但是不应该在文档里展开论述这些细节。 318 | 319 | 尤其应该挑选哪些条目可以在 rustdoc 展示出来 —— 320 | 展示那些让使用者完全掌握使用这个 crate 的内容, 321 | 其他的内容不展示。 322 | 下面代码中给 `PublicError` 实现 `From` 的 rustdoc 文档会默认展示出来, 323 | 使用 `#[doc(hidden)]` 来隐藏它,因为用户不会在代码里面用到私有的 `PrivateError` , 324 | 所以这个 impl 块对用户来说完全无关。 325 | 326 | ```rust,ignored 327 | // This error type is returned to users. 328 | pub struct PublicError { /* ... */ } 329 | 330 | // This error type is returned by some private helper functions. 331 | struct PrivateError { /* ... */ } 332 | 333 | // Enable use of `?` operator. 334 | #[doc(hidden)] 335 | impl From for PublicError { 336 | fn from(err: PrivateError) -> PublicError { 337 | /* ... */ 338 | } 339 | } 340 | ``` 341 | 342 | [`pub(crate)`] 是一个很棒的工具,它从公有 API 移除掉实现的细节:\ 343 | 让条目于 定义所在的模块之外 被使用,但 定义所在的 crate 之外 无法被使用。 344 | (仅让条目在 crate 内可见,在 crate 之外不可见) 345 | 346 | [`pub(crate)`]: https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md 347 | -------------------------------------------------------------------------------- /src/external-links.md: -------------------------------------------------------------------------------- 1 | # 拓展阅读 2 | 3 | - [RFC 199] - 所有权命名规范 (Ownership naming conventions) 4 | - [RFC 344] - 命名规范 (Naming conventions) 5 | - [RFC 430] - 命名规范 (Naming conventions) 6 | - [RFC 505] - 文档编写规范 (Doc conventions) 7 | - [RFC 1574] - 文档编写规范 (Doc conventions) 8 | - [RFC 1687] - crate 根文档 (Crate-level documentation) 9 | - [Elegant Library APIs in Rust](https://deterministic.space/elegant-apis-in-rust.html) 10 | :优雅地设计 Rust 库 APIs 11 | - [Rust Design Patterns](https://github.com/rust-unofficial/patterns) :Rust 设计模式 12 | 13 | [RFC 344]: https://github.com/rust-lang/rfcs/blob/master/text/0344-conventions-galore.md 14 | [RFC 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md 15 | [RFC 1687]: https://github.com/rust-lang/rfcs/pull/1687 16 | [RFC 505]: https://github.com/rust-lang/rfcs/blob/master/text/0505-api-comment-conventions.md 17 | [RFC 1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md 18 | [RFC 1574]: https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md 19 | [RFC 199]: https://github.com/rust-lang/rfcs/blob/master/text/0199-ownership-variants.md 20 | -------------------------------------------------------------------------------- /src/flexibility.md: -------------------------------------------------------------------------------- 1 | # 灵活性 2 | 3 | 4 | 5 | ## 为避免重复计算 函数应提供中间结果 6 | 7 | > Functions expose intermediate results to avoid duplicate work (C-INTERMEDIATE) 8 | 9 | 很多函数为了解决问题,会计算一些有趣且有关的数据。 10 | 如果这些数据可能引发使用者的兴趣, 11 | 请考虑在 API 里面返回它们。 12 | 13 | 来自标准库的例子: 14 | 15 | - [`Vec::binary_search`] 16 | 无论值有没有找到,它都不返回 `bool` 值,也不返回 `Option` 17 | 来表明可能找到的索引位置。实际上,在找到值的时候,它返回值的索引; 18 | 在没找到值的时候,它返回需要插入这个值的位置。 19 | 20 | - [`String::from_utf8`] 21 | 若传入的字节不是 UTF-8 的话,它运行失败,然后返回中间结果: 22 | 提供输入字节中第一个无效 UTF-8 序列的索引,也可以返回输入字节的所有权。 23 | 24 | - [`HashMap::insert`] 25 | 返回 `Option` ,如果预先存在一个值,那么返回这个值。 26 | 使用者如果想恢复插入操作之前的值,那么返回的值就避免用户二次查找哈希表了。 27 | 28 | [`Vec::binary_search`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.binary_search 29 | [`String::from_utf8`]: https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8 30 | [`HashMap::insert`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html#method.insert 31 | 32 | 33 | 34 | ## 调用方决定在何处复制和替换数据 35 | 36 | > Caller decides where to copy and place data (C-CALLER-CONTROL) 37 | 38 | 如果函数参数需要具有所有权, 39 | 那么直接获取所有权,而不要通过借用和复制的方式来获取所有权。[^C-CALLER-CONTROL] 40 | 41 | ```rust,ignored 42 | // 应该该这样做 43 | fn foo(b: Bar) { 44 | /* 直接使用 `b` 的所有权 */ 45 | } 46 | 47 | // 不要这样做 48 | fn foo(b: &Bar) { 49 | let b = b.clone(); 50 | /* 复制之后再拿到 `b` 的所有权 */ 51 | } 52 | ``` 53 | 54 | 如果函数参数不需要所有权,那就获取共享引用或独占引用, 55 | 不要获取所有权,然后把数据扔掉。 56 | 57 | ```rust,ignored 58 | // 应该该这样做 59 | fn foo(b: &Bar) { 60 | /* 使用借用 */ 61 | } 62 | 63 | // 不要这样做 64 | fn foo(b: Bar) { 65 | /* 使用借用,但是函数进行返回的时候,偷偷把 `b` 给 drop 掉了 */ 66 | } 67 | ``` 68 | `Copy` trait 应该在真正需要它的时候才使用它, 69 | 不要把它当做低成本复制的方式。 70 | 71 | [^C-CALLER-CONTROL]: 译者注:虽然其内容讲的是被调用方(函数), 72 | 但是这条原则是站在使用者 (caller) 角度来描述的。 73 | 因为用户可以选择(也可以不选择)复制一份具有所有权的数据再传入需要所有权的函数, 74 | 这个复制数据的选择权(决定权)在于调用方。 75 | 函数不应该做这个决定。 76 | 77 | 78 | ## 函数通过泛型来对参数做最小范围的假设 79 | 80 | > Functions minimize assumptions about parameters by using generics (C-GENERIC) 81 | 82 | 对函数输入做越小范围的假设, 83 | 函数的使用场景就越广泛: 84 | 85 | 如果函数只需要迭代类型的数据,请这样写: 86 | 87 | ```rust,ignored 88 | fn foo>(iter: I) { /* ... */ } 89 | ``` 90 | 91 | 而不要详细到这般: 92 | 93 | ```rust,ignored 94 | fn foo(c: &[i64]) { /* ... */ } 95 | fn foo(c: &Vec) { /* ... */ } 96 | fn foo(c: &SomeOtherCollection) { /* ... */ } 97 | ``` 98 | 99 | 一般来说,考虑使用泛型来准确表明函数对参数的假设关系是什么。 100 | 101 | ### 泛型的优点 102 | 103 | * **可复用**:泛型函数能应用在广泛的类型上,同时明确给出了这些类型的必须满足的关系。 104 | * **静态分派和编译器优化**: 105 | 每个泛型函数都被专门用于实现了 trait bounds 的具体的类型 106 | (即 单态化 [monomorphized] ),这意味着: 107 | 1. 调用的 trait 方法是静态生成的,因此是直接对 trait 实现的调用 108 | 2. 编译器能对这些调用做内联 (inline) 和其他优化 109 | * **内联式布局**:如果结构体和枚举体类型具有某个泛型参数 `T` , 110 | `T` 的值将在结构体和枚举体里以内联方式排列,不产生任何间接调用。 111 | * **可推断**:由于泛型函数的类型参数通常是推断出来的, 112 | 泛型函数可以减少复杂的代码,比如显式转换、通常必须的一些方法调用。 113 | * **精确的类型**:因为泛型给实现了某个 trait 的具体类型一个名称, 114 | 从而有可能清楚这个类型需要或创建的地方在哪。比如这个函数: 115 | 116 | ```rust,ignored 117 | fn binary(x: T, y: T) -> T 118 | ``` 119 | 120 | 会保证消耗和创建具有相同类型 `T` 的值;不可能传入实现了 `Trait` 121 | 的但不同名称的两个类型。 122 | 123 | [monomorphized]: https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics 124 | 125 | ### 泛型的缺点 126 | 127 | * **增加代码大小**:单态化泛型函数意味着函数体会被复制。 128 | 增加代码大小和静态分派的性能优势之间必须做出衡量。 129 | * **类型同质化**:这是 “精确的类型” 带来的另一面: 130 | 如果 `T` 是类型参数,那么它代表一个单独的实际类型。 131 | 对于像 `Vec` 这样具体的单独的元素类型也是一样, 132 | 而且 `Vec` 实际上为了内联这些元素,进行了专门的处理。 133 | 有时候,不同的类型会更有用,参考 [trait objects][C-OBJECT] 。 134 | * **签名冗余**:过度使用泛型会造成阅读和理解函数签名更困难。 135 | 136 | [C-OBJECT]: #c-object 137 | 138 | 来自标准库的例子: 139 | 140 | - [`std::fs::File::open`] 以泛型 `AsRef` 作为参数。 141 | 它能方便根据 `"f.txt"` 这样的字符串字面值、 [`Path`] 、 [`OsString`] 142 | 以及其他一些类型中打开文件。 143 | 144 | [`std::fs::File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open 145 | [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 146 | [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html 147 | 148 | 149 | 150 | ## trait 用作 object 时应当是安全的 151 | 152 | > Traits are object-safe if they may be useful as a trait object (C-OBJECT) 153 | 154 | trait object 有一些很重要的限制: 155 | 1. 通过 trait object 调用的方法不能使用泛型; 156 | 2. 除了接收者位置上可以使用 `Self`,其他地方不能使用 `Self` (比如 返回值类型)。 157 | 158 | 设计 trait 的时候,早些决定这个 trait 要作为 object 还是作为泛型的 bound 来使用。 159 | 160 | 如果 trait 用作 object ,它的方法应该被传给和返回 trait objects , 161 | 而不是泛型。 162 | 163 | 带有 `Self: Sized` 的 `where` 语句可以用来把某个具体的方法从 trait object 164 | 里排除掉 (exclude) 。下面这个 trait 不是安全的 ([object-safe]) , 165 | 因为具有泛型方法。 166 | 167 | ```rust,ignored 168 | trait MyTrait { 169 | fn object_safe(&self, i: i32); 170 | 171 | fn not_object_safe(&self, t: T); 172 | } 173 | 174 | fn f() -> Box { /* 代码 */ } 175 | ``` 176 | 177 | 增加所需的 `Self: Sized` 来把这个泛型方法从 trait object 里排除掉, 178 | 从而让 trait 是安全的。 179 | 180 | ```rust,ignored 181 | trait MyTrait { 182 | fn object_safe(&self, i: i32); 183 | 184 | fn not_object_safe(&self, t: T) where Self: Sized; 185 | } 186 | 187 | fn f() -> Box { /* 代码 */ } 188 | ``` 189 | 190 | ### trait objects 的优点 191 | 192 | * **异质性**:当你需要 trait object 的时候,的确真的需要它。 193 | * **代码体积小**:不像泛型, trait objects 不生成处理过的代码(单态化), 194 | 所以能很大程度减少代码体积。 195 | 196 | ### trait objects 的缺点 197 | 198 | * **无泛型方法**: trait objects 现在无法提供泛型方法。 199 | * **动态分派和胖指针**: trait objects 天生就涉及间接操作,所以具有性能惩罚。 200 | * **没有 `Self`** : 除了接收者位置上可以使用,其他地方不能使用 `Self` 类型。 201 | 202 | 来自标准库的例子: 203 | 204 | - [`io::Read`] 和 [`io::Write`] trait 常用作 trait objects 205 | - [`Iterator`] trait 有多个具有 `where Self: Sized` 标记的泛型方法, 206 | 目的是能让 `Iterator` 用作 trait object 207 | 208 | [`io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html 209 | [`io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html 210 | [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html 211 | 212 | [object-safe]: https://doc.rust-lang.org/nightly/reference/items/traits.html#object-safety 213 | -------------------------------------------------------------------------------- /src/future-proofing.md: -------------------------------------------------------------------------------- 1 | # 前瞻性 2 | 3 | 4 | 5 | ## 封装的 traits 隔绝下游的实现 6 | 7 | > Sealed traits protect against downstream implementations (C-SEALED) 8 | 9 | 有些 traits 仅仅在定义它们的 crate 里使用。 10 | 这时,我们依然能通过封装 trait 的方式修改 trait 代码, 11 | 从而不破坏现有的其他代码。 12 | 13 | ```rust,ignored 14 | /// 这个 trait 被封装起来了,当前 crate 之外无法给类型实现 `private::Sealed` 15 | pub trait TheTrait: private::Sealed { 16 | // 给使用者使用的方法,数量可以是 0 或更多 17 | fn ...(); 18 | 19 | // 不给使用者使用的方法,数量可以是 0 或更多 20 | #[doc(hidden)] 21 | fn ...(); 22 | } 23 | 24 | // 给类型实现 `TheTrait` 25 | impl TheTrait for usize { 26 | /* ... */ 27 | } 28 | 29 | mod private { 30 | pub trait Sealed {} 31 | 32 | // 给相同的类型实现 `Sealed` ,别的类型不实现 `Sealed` 33 | impl Sealed for usize {} 34 | } 35 | ``` 36 | 37 | `Sealed` 是没有方法的、私有的、`TheTrait` 的父 trait (supertrait) , 38 | 下游 crates 无法与 `Sealed` 有一样的命名(因为绝对路径),从而保证了 `Sealed` 的实现 39 | (也是 `TheTrait` 的实现) 只存在于当前 crate 。 40 | 我们能自由地给 `TheTrait` 添加方法,这种 “非破坏性” 的修改, 41 | 倘若在未封装 traits 的情况下 通常是重大的变更。 42 | 此外,我们能自由地修改不被公开说明的方法签名(这不是私有的)。 43 | 44 | 注意,在封装的 trait 里 移除公开的方法或者改变公开方法的签名 45 | 仍然是破坏性的修改。 46 | 47 | 为了避免打消使用 trait 的人积极性,封装的 trait 和当前 crate 之外不能实现的 trait 48 | 应该用 rustdoc 加以说明。 49 | 50 | 例子: 51 | 52 | - [`serde_json::value::Index`](https://docs.serde.rs/serde_json/value/trait.Index.html) 53 | - [`byteorder::ByteOrder`](https://docs.rs/byteorder/1.1.0/byteorder/trait.ByteOrder.html) 54 | 55 | 56 | 57 | ## 结构体具有私有字段 58 | 59 | > Structs have private fields (C-STRUCT-PRIVATE) 60 | 61 | 让结构体字段公开,这是一个强有力的承诺: 62 | 确定了选择什么来呈现给使用者, 63 | 并且让结构体 免于提供各种验证,也不必保持字段内容不变, 64 | 因为使用者可以任意修改结构体的公开的字段数据。 65 | 66 | 公开的字段最适合 C 语言风格的结构体: 67 | 复合、被动的数据结构([PDS])。 68 | 除此之外的场景,请考虑隐藏字段,然后提供 getter/setter 方法。 69 | 70 | [PDS]:https://en.wikipedia.org/wiki/Passive_data_structure 71 | 72 | 73 | ## newtypes 封装起实现的细节 74 | 75 | > Newtypes encapsulate implementation details (C-NEWTYPE-HIDE) 76 | 77 | newtype 在隐藏实现细节的同时,还可以给使用者提供精简而确切的代码。 78 | 79 | 比如,下面的 `my_transform` 函数返回复合的迭代器类型。 80 | 81 | ```rust,ignored 82 | use std::iter::{Enumerate, Skip}; 83 | 84 | pub fn my_transform(input: I) -> Enumerate> { 85 | input.skip(3).enumerate() 86 | } 87 | ``` 88 | 89 | 如果想要向使用者隐藏这个类型 —— 从使用者的角度看返回的类型是粗略的 90 | `Iterator` ,那么可以使用 newtype : 91 | 92 | ```rust,ignored 93 | use std::iter::{Enumerate, Skip}; 94 | 95 | pub struct MyTransformResult(Enumerate>); 96 | 97 | impl Iterator for MyTransformResult { 98 | type Item = (usize, I::Item); 99 | 100 | fn next(&mut self) -> Option { 101 | self.0.next() 102 | } 103 | } 104 | 105 | pub fn my_transform(input: I) -> MyTransformResult { 106 | MyTransformResult(input.skip(3).enumerate()) 107 | } 108 | ``` 109 | 110 | 除了简化函数签名, newtype 还向使用者提供更少的细节代码。 111 | 用户不知道返回结果的迭代器是如何构造和呈现的, 112 | 这意味着将来可以在不破坏用户代码的情况下,修改这处封装的部分。 113 | 114 | Rust 1.26 引入了 [`impl Trait`] 语法, 115 | 这比 newtype 模式更简明,但也带来一些缺点, 116 | 即你会被限制在 `impl Trait` 的表达里 而不够自由。 117 | 比如在返回实现了 `Debug` 或 `Clone` 或组合其他 trait 的迭代器类型时会遇到麻烦。 118 | 总之, `impl Trait` 用在返回值类型上,在内部 APIs 里使用它或许很不错, 119 | 甚至在公开的 APIs 里使用它更合适,然而并不是所有场合都合适。 120 | 参考 *版本指南* 的 ["`impl Trait` for returning complex types with ease"][impl-trait-3] 121 | 部分 和 [`impl Trait` 发行记录][impl-trait-3] 来找到更多细节。 122 | 123 | 124 | [`impl Trait`]: https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md 125 | [impl-trait-2]: https://doc.rust-lang.org/edition-guide/rust-2018/trait-system/impl-trait-for-returning-complex-types-with-ease.html 126 | [impl-trait-3]: https://blog.rust-lang.org/2018/05/10/Rust-1.26.html#impl-trait 127 | 128 | ```rust,ignored 129 | pub fn my_transform(input: I) -> impl Iterator { 130 | input.skip(3).enumerate() 131 | } 132 | ``` 133 | 134 | 135 | 136 | ## 已经 `derive` 的数据结构不应该再使用 trait bounds 137 | 138 | > Data structures do not duplicate derived trait bounds (C-STRUCT-BOUNDS) 139 | 140 | 141 | 从 `derive` 属性获得 traits 时, 142 | 泛型数据结构不应该使用 trait bounds 再次获取 trait , 143 | 也不要用另外语义的增加值。 144 | `derive` 属性里的每个 trait 都只对实现了此 trait 的泛型参数展开成单独的 `impl` 块。 145 | 146 | ```rust,ignored 147 | // Prefer this: 148 | #[derive(Clone, Debug, PartialEq)] 149 | struct Good { /* ... */ } 150 | 151 | // Over this: 152 | #[derive(Clone, Debug, PartialEq)] 153 | struct Bad { /* ... */ } 154 | ``` 155 | 156 | 像 `Bad` 类型那样重复实现已获得的 trait 是不必要的, 157 | 而且会导致向后不兼容。 158 | 为了理解这一点,基于前面的例子,考虑给结构体实现 `PartialOrd` trait : 159 | 160 | ```rust,ignored 161 | // Non-breaking change: 162 | #[derive(Clone, Debug, PartialEq, PartialOrd)] 163 | struct Good { /* ... */ } 164 | 165 | // Breaking change: 166 | #[derive(Clone, Debug, PartialEq, PartialOrd)] 167 | struct Bad { /* ... */ } 168 | ``` 169 | 170 | 一般来说,给数据结构增加 trait bound 是非兼容性更改, 171 | 因为使用这个结构的人需要修改代码来满足额外的 bound 。 172 | 使用 `derive` 属性来增加标准库里的 trait 是兼容性更改。 173 | 174 | 以下 traits 绝不应该用在数据结构的 trait bounds 里: 175 | 176 | - `Clone` 177 | - `PartialEq` 178 | - `PartialOrd` 179 | - `Debug` 180 | - `Display` 181 | - `Default` 182 | - `Serialize` 183 | - `Deserialize` 184 | - `DeserializeOwned` 185 | 186 | 对于 `derive` 不支持的 trait, 187 | 定义数据结构时需不需要使用 trait bounds 没有严格而清晰的结论。 188 | 比如 `Read` 和 `Write` trait ,它们在定义结构时 既能传达类型预期的行为, 189 | 又能限制住将来拓展新的 trait 。 190 | 而且在数据结构上使用 trait bounds 会比使用 `derive` traits 来限制泛型更简单。 191 | 192 | --- 193 | 194 | 但是 有三种情况 **必须** 使用 trait bounds 语法: 195 | 196 | 1. 数据结构的关联类型是基于 trait 的时候。 197 | 2. 限制泛型的 trait 是 `?Sized` 的时候。 198 | 3. 已经有 `Drop` impl 的数据结构,在需要用 trait 限制泛型的时候。 \ 199 | Rust 目前要求所有数据结构上的泛型 trait bounds 都要出现在 `Drop` impl 上。[^drop-impl] 200 | 201 | 来自标准库的例子: 202 | 203 | 1. [`std::borrow::Cow`] 指向基于 `Borrow` trait 的关联类型 204 | 2. [`std::boxed::Box`] 在 `Sized` bound 之外进行操作 205 | 3. [`std::io::BufWriter`] 在 `Drop` impl 里需要 trait bound 206 | 207 | [`std::borrow::Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html 208 | [`std::boxed::Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html 209 | [`std::io::BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html 210 | [`std::io::BufWriter`-impl-Drop]: https://doc.rust-lang.org/src/std/io/buffered/bufwriter.rs.html#150-156 211 | 212 | [^drop-impl]: 译者注:这句话很难理解和翻译,可能来自于 [issue 6] 。译者是观察 213 | `std::io::BufWriter` 的 `impl Drop` [源码][`std::io::BufWriter`-impl-Drop] 214 | 而做出这句翻译。正文原话是\ 215 | *Rust currently requires all trait bounds on the `Drop` impl are also present 216 | on the data structure.* 217 | 218 | [issue 6]: https://github.com/rust-lang/api-guidelines/issues/6 219 | -------------------------------------------------------------------------------- /src/interoperability.md: -------------------------------------------------------------------------------- 1 | # 互通互用 2 | 3 | 4 | 5 | ## 类型应尽早实现常见的 traits 6 | 7 | > Types eagerly implement common traits (C-COMMON-TRAITS) 8 | 9 | Rust 的 trait 系统坚持 [孤儿原则][orphan] :大致说的是, 10 | 每个 `impl` 块必须 11 | 1. 要么存在于定义 trait 的 crate 中, 12 | 2. 要么存在于给类型实现 trait 的 crate 中。 13 | 14 | 所以,定义新类型的 crates 应该尽早实现所有合适的、常见的 traits 。 15 | 16 | 为什么呢?想想下面的情况: 17 | 18 | * `std` crate 定义了 `Display` trait 。 19 | * `url` crate 定义了 `Url` 类型,该类型没有实现 `Display` trait 。 20 | * `webapp` crate 导入 `std` 和 `url` crate , 21 | 22 | 然而,没法在 `webapp` crate 中给 `Url` 增加 `Display` trait, 23 | 因为类型和 trait 都不在 `webapp` 中定义。\ 24 | (注意:[newtype] 模式可以提供一个高效但不太方便的替代办法。) 25 | 26 | [orphan]:https://doc.rust-lang.org/book/ch10-02-traits.html?highlight=orphan#implementing-a-trait-on-a-type 27 | [newtype]:https://doc.rust-lang.org/book/ch19-03-advanced-traits.html?highlight=orphan#using-the-newtype-pattern-to-implement-external-traits-on-external-types 28 | 29 | `std` 中可给类型实现的、最重要的、常见的 traits 有: 30 | 31 | - [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html) 32 | - [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) 33 | - [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html) 34 | - [`PartialEq`](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html) 35 | - [`Ord`](https://doc.rust-lang.org/std/cmp/trait.Ord.html) 36 | - [`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html) 37 | - [`Hash`](https://doc.rust-lang.org/std/hash/trait.Hash.html) 38 | - [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) 39 | - [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) 40 | - [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) 41 | 42 | 给类型实现 `Default` trait 和空的 `new` 构造函数是常见和有必要的。\ 43 | `new` 是 Rust 中常规的构造函数,所以不使用参数来构造基本的类型时, 44 | `new` 对使用者来说就理应存在。\ 45 | `default` 方法功能上与 `new` 方法一致,所以也应当存在。 46 | 47 | 48 | 49 | ## 使用 `From`, `AsRef`, `AsMut` trait 来转换类型 50 | 51 | > Conversions use the standard traits `From`, `AsRef`, `AsMut` (C-CONV-TRAITS) 52 | 53 | 以下转换类型的 traits 在合理的时候 **应该** 被实现: 54 | 55 | - [`From`](https://doc.rust-lang.org/std/convert/trait.From.html) 56 | - [`TryFrom`](https://doc.rust-lang.org/std/convert/trait.TryFrom.html) 57 | - [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) 58 | - [`AsMut`](https://doc.rust-lang.org/std/convert/trait.AsMut.html) 59 | 60 | 以下转换类型的 traits 在任何时候都 **不应该** 被实现: 61 | 62 | - [`Into`](https://doc.rust-lang.org/std/convert/trait.Into.html) 63 | - [`TryInto`](https://doc.rust-lang.org/std/convert/trait.TryInto.html) 64 | 65 | 因为这两个 traits 基于 `From` 、 `TryFrom` trait 进行覆盖实现 (blanket impl) , 66 | 所以只需要实现 `From` 、 `TryFrom` 。 67 | 68 | 来自标准库的例子: 69 | 70 | - `u32` 实现了 `From` ,因为更小范围的整数总是可以转化为更大范围的整数。 71 | - `u16` 没有实现 `From` ,因为如果超过 `u16` 范围的整数不可能转化为 `u16` 。 72 | - `u16` 实现了 `TryFrom` ,如果整数大于 `u16` 的范围,那么返回错误。 73 | - [`IpAddr`] 实现了 [`From`] ,[`IpAddr`] 能够以 v4 和 v6 两种 IP 地址表示。 74 | 75 | [`From`]: https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html 76 | [`IpAddr`]: https://doc.rust-lang.org/std/net/enum.IpAddr.html 77 | 78 | 79 | 80 | ## 给集合实现 `FromIterator` 和 `Extend` trait 81 | 82 | > Collections implement `FromIterator` and `Extend` (C-COLLECT) 83 | 84 | [`FromIterator`] 和 [`Extend`] trait 让集合 (collections) 方便地使用以下迭代器方法: 85 | 86 | [`FromIterator`]: https://doc.rust-lang.org/std/iter/trait.FromIterator.html 87 | [`Extend`]: https://doc.rust-lang.org/std/iter/trait.Extend.html 88 | 89 | - [`Iterator::collect`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect) 90 | - [`Iterator::partition`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.partition) 91 | - [`Iterator::unzip`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.unzip) 92 | 93 | `FromIterator` 从迭代器中生成新的集合, 94 | `Extend` 从迭代器中追加元素到已存在的集合。 95 | 96 | 来自标准库的例子: 97 | 98 | - [`Vec`] 实现了 `FromIterator` 和 `Extend` 。 99 | 100 | [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html 101 | 102 | 103 | 104 | ## 给数据结构实现 Serde 的 `Serialize` 和 `Deserialize` trait 105 | 106 | > Data structures implement Serde's `Serialize`, `Deserialize` (C-SERDE) 107 | 108 | 具有数据结构功能的类型应该实现 `Serialize` 和 `Deserialize` trait 。 109 | 110 | [`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html 111 | [`Deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html 112 | 113 | 对于数据结构 (data structure) ,界定显然是和显然不是 **之间** 的类型是很困难的。\ 114 | 115 | 这两个是界限分明的例子:\ 116 | [`LinkedHashMap`] 和 [`IpAddr`] 是数据结构, 117 | 从 JSON 中读取 `LinkedHashMap` / `IpAddr` , 118 | 或者通过 IPC 发送其中一种数据到另一个进程 都十分合理。\ 119 | [`LittleEndian`] 不是数据结构,它在 `byteorder` crate 中被用来编译期优化字节, 120 | 尤其是字节的顺序,而且实际上 `LittleEndian` 永远不存在于运行时。 121 | 122 | 而 #rust 和 #serde IRC 通信就是很让这种界限模糊的情况得到良好的处理。 123 | 如果某个 crate 因为某些原因还没有把 Serde 作为依赖,那么它可能希望把 Serde 相关的 124 | impls 块放在 Cargo cfg 后面用作条件编译。从而下游的库需要这些 Serde impls 的时候, 125 | 就编译 Serde ,不需要的时候不编译 Serde 。 126 | 127 | 为了和其他基于 Serde 的库一致,Cargo cfg 的名称应该是简单的 `"serde"` , 128 | 而不要使用 `"serde_impls"` 或 `"serde_serialization"` 当作名称。 129 | 130 | [`LinkedHashMap`]: https://docs.rs/linked-hash-map/0.4.2/linked_hash_map/struct.LinkedHashMap.html 131 | [`IpAddr`]: https://doc.rust-lang.org/std/net/enum.IpAddr.html 132 | [`LittleEndian`]: https://docs.rs/byteorder/1.0.0/byteorder/enum.LittleEndian.html 133 | 134 | 不使用 derive 参数的话,通常实现方式如下: 135 | 136 | ```toml 137 | [dependencies] 138 | serde = { version = "1.0", optional = true } 139 | ``` 140 | 141 | ```rust,ignored 142 | #[cfg(feature = "serde")] 143 | extern crate serde; 144 | 145 | struct T { /* ... */ } 146 | 147 | #[cfg(feature = "serde")] 148 | impl Serialize for T { /* ... */ } 149 | 150 | #[cfg(feature = "serde")] 151 | impl<'de> Deserialize<'de> for T { /* ... */ } 152 | ``` 153 | 154 | 使用 derive 参数的话,实现方式为: 155 | 156 | ```toml 157 | [dependencies] 158 | serde = { version = "1.0", optional = true, features = ["derive"] } 159 | ``` 160 | 161 | ```rust,ignored 162 | #[cfg(feature = "serde")] 163 | #[macro_use] 164 | extern crate serde; 165 | 166 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 167 | struct T { /* ... */ } 168 | ``` 169 | 170 | 171 | 172 | ## 类型应尽可能实现 `Send` 和 `Sync` trait 173 | 174 | > Types are `Send` and `Sync` where possible (C-SEND-SYNC) 175 | 176 | 在编译器确认类型适合实现 `Send` 和 `Sync` trait 的时候,它们是自动实现的。 177 | 178 | [`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html 179 | [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html 180 | 181 | 对于操纵裸指针 (raw pointers) 的类型,实现这两个 trait 就要十分谨慎, 182 | 因为它们表明是线程安全的。\ 183 | 像下面的单元测试有助于察觉到是否因 `Send` 或 `Sync` 导致无意间的性能倒退。 184 | 185 | ```rust,ignored 186 | #[test] 187 | fn test_send() { 188 | fn assert_send() {} 189 | assert_send::(); 190 | } 191 | 192 | #[test] 193 | fn test_sync() { 194 | fn assert_sync() {} 195 | assert_sync::(); 196 | } 197 | ``` 198 | 199 | 200 | 201 | ## Error 类型 十分直观和有用 202 | 203 | > Error types are meaningful and well-behaved (C-GOOD-ERR) 204 | 205 | Error 类型 是 `Result` 中的 `E` 类型,在你的 crate 中 206 | `Result` 是被公共 (public) 函数返回的。\ 207 | Error 类型 总是应该实现 [`std::error::Error`] trait :\ 208 | 在错误处理库中用这个 trait 来对不同类型的错误进行抽象(比如 [`error-chain`] crate );\ 209 | 也可以使用这个 trait 的 [`.source()`] 方法来得到更底层的错误。 210 | 211 | [`std::error::Error`]: https://doc.rust-lang.org/std/error/trait.Error.html 212 | [`error-chain`]: https://docs.rs/error-chain 213 | [`source()`]: https://doc.rust-lang.org/std/error/trait.Error.html#method.source 214 | 215 | 此外,Error 类型 应该实现 [`Send`] 和 [`Sync`] trait 。\ 216 | 不具备 [`Send`] 的 Error 类型 不能被 [`thread::spawn`] 运行的线程返回;\ 217 | 不具备 [`Sync`] 的 Error 类型 不能在不同的线程间使用 [`Arc`] 来传递。\ 218 | 这些在多线程应用软件中处理基础错误时是常见且必须的。 219 | 220 | [`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html 221 | [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html 222 | [`thread::spawn`]: https://doc.rust-lang.org/std/thread/fn.spawn.html 223 | [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html 224 | 225 | `Send` 和 `Sync` 在使用 [`std::io::Error::new`] 进行自定义 IO 错误时扮演了重要角色, 226 | 因为这里传入的 Error 类型 需要实现 `Error + Send + Sync` trait 。 227 | 228 | [`std::io::Error::new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new 229 | 230 | 在返回 Error trait 对象的函数里,需要对这条原则保持审慎。 231 | 比如 [`reqwest::Error::get_ref`] 函数。 232 | 通常 `Error + Send + Sync + 'static` 组合适合对大多数调用场景。 233 | 附加的 `'static` 能利用 [`Error::downcast_ref`] 方法来获取 Error trait 对象 。 234 | 235 | 236 | [`reqwest::Error::get_ref`]: https://docs.rs/reqwest/0.7.2/reqwest/struct.Error.html#method.get_ref 237 | [`Error::downcast_ref`]: https://doc.rust-lang.org/std/error/trait.Error.html#method.downcast_ref-2 238 | 239 | 不要把 `()` 作为 Error 类型 ,即便对于一个不带有帮助性的额外信息的错误, 240 | 也不要让这个错误是 `()` 类型 。 241 | 242 | - `()` 没有实现 `Error` ,所以它无法和 `error-chain` 这样的错误处理库一起使用。 243 | - `()` 没有实现 `Display` ,所以使用者无法打印自己写的错误信息。 244 | - `()` 实现了 `Debug` ,但是当使用者 `unwrap` 错误的时候,它没提供任何有用的东西。 245 | - 对于实现了 `From<()>` 的 Error 类型的下游库 ,`()` 在语义上是毫无意义的, 246 | 从而无法使用 `?` 操作符。 247 | 248 | 需要做的是,针对你的 crate 或者单个函数来定义有实际意义的 Error 类型 。 249 | 给合适的 Error 类型 实现 `Error` 和 `Display` trait 。 250 | 对于无实际意义的错误,利用 unit 结构体来实现 `Error` 和 `Display` trait 。 251 | 252 | ```rust,ignored 253 | use std::error::Error; 254 | use std::fmt::Display; 255 | 256 | // Instead of this... 257 | fn do_the_thing() -> Result 258 | 259 | // Prefer this... 260 | fn do_the_thing() -> Result 261 | 262 | #[derive(Debug)] 263 | struct DoError; 264 | 265 | impl Display for DoError { /* ... */ } 266 | impl Error for DoError { /* ... */ } 267 | ``` 268 | 269 | Error 类型 实现的 `Display` trait 通常应该提供简明的错误信息 (error messages), 270 | 而且信息用全小写,末尾不带标点符号。\ 271 | 不应该实现 [`Error::description()`] trait ,因为它已经被弃用。 272 | 而应该实现 `Display` trait 来打印错误[^Display]。 273 | 274 | [`Error::description()`]: https://doc.rust-lang.org/std/error/trait.Error.html#tymethod.description 275 | 276 | 来自标准库的例子: 277 | 278 | - [`ParseBoolError`] :从字符串解析 bool 值时返回这个错误。 279 | 280 | [`ParseBoolError`]: https://doc.rust-lang.org/std/str/struct.ParseBoolError.html 281 | 282 | 一些错误信息的例子: 283 | 284 | - "unexpected end of file" 285 | - "provided string was not \`true\` or \`false\`" 286 | - "invalid IP address syntax" 287 | - "second time provided was later than self" 288 | - "invalid UTF-8 sequence of {} bytes from index {}" 289 | - "environment variable was not valid unicode: {:?}" 290 | 291 | 292 | [^Display]: 译者注: 293 | [`ToString`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string) 294 | 给所有具有 `Display` trait 的类型自动实现了 `.to_string()` 方法,从而获得错误信息的字符串。 295 | 296 | 297 | ## 二进制数类型应提供 `Hex`, `Octal`, `Binary` 的格式化方式 298 | 299 | > Binary number types provide `Hex`, `Octal`, `Binary` formatting (C-NUM-FMT) 300 | 301 | - [`std::fmt::UpperHex`](https://doc.rust-lang.org/std/fmt/trait.UpperHex.html) 302 | - [`std::fmt::LowerHex`](https://doc.rust-lang.org/std/fmt/trait.LowerHex.html) 303 | - [`std::fmt::Octal`](https://doc.rust-lang.org/std/fmt/trait.Octal.html) 304 | - [`std::fmt::Binary`](https://doc.rust-lang.org/std/fmt/trait.Binary.html) 305 | 306 | 这些 traits 在 `{:X}`, `{:x}`, `{:o}`, `{:b}` 格式化分类符 (format specifiers) 307 | 下用来控制类型的呈现方式。 308 | 309 | 对你认为会进行 位操作(比如 `|` 、 `&`)的任何数字类型实现这些 traits —— 310 | 尤其适合 位标志 (bitflag) 类型。 311 | 像 `struct Nanoseconds(u64)` 这样的数值单位类型就可能无需实现这些 trait 。 312 | 313 | 314 | ## reader/writer 泛型函数使用 `R: Read` 和 `W: Write` 参数传值 315 | 316 | > Generic reader/writer functions take `R: Read` and `W: Write` by value (C-RW-VALUE) 317 | 318 | 标准库里有以下两个 impl 块: 319 | 320 | ```rust,ignored 321 | impl<'a, R: Read + ?Sized> Read for &'a mut R { /* ... */ } 322 | 323 | impl<'a, W: Write + ?Sized> Write for &'a mut W { /* ... */ } 324 | ``` 325 | 326 | 这意味着接收 `R: Read` 或者 `W: Write` 泛型参数值的 所有函数 327 | 都可以在需要的时候传入 可变引用 (mut reference) 。 328 | 329 | 在这些函数的文档里,需要简要地提醒使用者可以传入可变引用。 330 | Rust 新手经常对此感到疑惑。 331 | 已经打开了文件,并且想从文件中读取数据的时候,新手往往卡在这一步。 332 | 解决的办法是,根据上面两种情况的一种,传入 `&mut f` , 333 | 而不是传入 `f` 作为参数。 334 | 335 | 例子: 336 | 337 | - [`flate2::read::GzDecoder::new`] 338 | - [`flate2::write::GzEncoder::new`] 339 | - [`serde_json::from_reader`] 340 | - [`serde_json::to_writer`] 341 | 342 | [`flate2::read::GzDecoder::new`]: https://docs.rs/flate2/1.0.20/flate2/read/struct.GzDecoder.html#method.new 343 | [`flate2::write::GzEncoder::new`]: https://docs.rs/flate2/1.0.20/flate2/write/struct.GzEncoder.html#method.new 344 | [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html 345 | [`serde_json::to_writer`]: https://docs.serde.rs/serde_json/fn.to_writer.html 346 | -------------------------------------------------------------------------------- /src/macros.md: -------------------------------------------------------------------------------- 1 | # 宏 2 | 3 | 4 | 5 | ## 输入语法与输出语法一致 6 | 7 | > Input syntax is evocative of the output (C-EVOCATIVE) 8 | 9 | Rust 宏系统允许你创造出你想要的输入语法。 10 | 尽量让输入的语法是让大多数人感到熟悉的, 11 | 仿照现存的 Rust 语法以便让使用者的代码风格统一。 12 | 注意对关键字和标点符号的选择与更改。 13 | 14 | 好的标准是:宏输入的语法与输出的结果不会相差太大,尤其在关键字和标点符号方面。 15 | 16 | 比如你写的宏要根据输入来声明一个具体的结构体,那么就以 `struct` 关键字开头, 17 | 后接结构体的名称。 18 | 19 | ```rust,ignored 20 | // 这种方式更好 21 | bitflags! { 22 | struct S: u32 { /* ... */ } 23 | } 24 | 25 | // 这种方式就不太好 26 | bitflags! { 27 | S: u32 { /* ... */ } 28 | } 29 | 30 | // 这种方式也不太好 31 | bitflags! { 32 | flags S: u32 { /* ... */ } 33 | } 34 | ``` 35 | 36 | 另一个例子是分号和逗号之间如何做出选择。 37 | Rust 的常量 (constants) 以分号作为结尾,所以如果你写的宏要定义一系列常量, 38 | 那么应该在输入语法里书写分号,即使语法看起来与 Rust 的语法有些不同。 39 | 40 | ```rust,ignored 41 | // 原始的常量定义语法后面接的是分号 42 | const A: u32 = 0b000001; 43 | const B: u32 = 0b000010; 44 | 45 | // 所以这种方式更好 46 | bitflags! { 47 | struct S: u32 { 48 | const C = 0b000100; 49 | const D = 0b001000; 50 | } 51 | } 52 | 53 | // 这种方式不太好 54 | bitflags! { 55 | struct S: u32 { 56 | const E = 0b010000, 57 | const F = 0b100000, 58 | } 59 | } 60 | ``` 61 | 62 | 宏是多样化的,以致于这里举的例子不会适合所有场景。 63 | 但是的确要仔细思考如何应用同一套规则。 64 | 65 | 66 | 67 | ## 宏与属性形成有机的整体 68 | 69 | > Item macros compose well with attributes (C-MACRO-ATTR) 70 | 71 | 生成超过一个条目 ([item]) 的宏,应该支持对每个条目添加属性。 72 | 一个常见的例子是,把单独的条目放在 cfg 后面: 73 | 74 | [item]:https://doc.rust-lang.org/nightly/reference/items.html 75 | 76 | ```rust,ignored 77 | bitflags! { 78 | struct Flags: u8 { 79 | #[cfg(windows)] 80 | const ControlCenter = 0b001; 81 | #[cfg(unix)] 82 | const Terminal = 0b010; 83 | } 84 | } 85 | ``` 86 | 87 | 生成结构体或枚举体的宏,应该支持添加宏属性, 88 | 方便让输出结果使用 derive 属性。 89 | 90 | ```rust,ignored 91 | bitflags! { 92 | #[derive(Default, Serialize)] 93 | struct Flags: u8 { 94 | const ControlCenter = 0b001; 95 | const Terminal = 0b010; 96 | } 97 | } 98 | ``` 99 | 100 | 101 | 102 | ## 生成条目的宏可以在条目被允许的地方使用 103 | 104 | > Item macros work anywhere that items are allowed (C-ANYWHERE) 105 | 106 | Rust 允许条目 ([item]) 有模块级别那样的作用域或者出现在类似函数这样小的作用域里。 107 | 生成条目的宏应该和普通的条目一样,在这些地方可以正常使用。 108 | 测试这些宏的时,应该至少在模块和函数作用域里都调用宏。 109 | 110 | ```rust,ignored 111 | #[cfg(test)] 112 | mod tests { 113 | test_your_macro_in_a!(module); 114 | 115 | #[test] 116 | fn anywhere() { 117 | test_your_macro_in_a!(function); 118 | } 119 | } 120 | ``` 121 | 122 | 下面这个例子展示了错误的宏: 123 | 这个宏能在模块作用域里运行,但不能在函数作用域里运行。 124 | 125 | ```rust,ignored 126 | macro_rules! broken { 127 | ($m:ident :: $t:ident) => { 128 | pub struct $t; 129 | pub mod $m { 130 | pub use super::$t; 131 | } 132 | } 133 | } 134 | 135 | broken!(m::T); // 运行通过,可以展开成 T 和 m::T 136 | 137 | fn g() { 138 | broken!(m::U); // 编译失败,super::U 指的是上级模块,而不是函数 g 139 | } 140 | ``` 141 | 142 | 143 | 144 | ## 生成条目的宏应支持可见性分类符 145 | 146 | > Item macros support visibility specifiers (C-MACRO-VIS) 147 | 148 | 宏应遵循 Rust 对条目可见性 ([visibility]) 的语法要求: 149 | 默认是私有的,如果使用 `pub` 则表明条目是公有的。 150 | 151 | [visibility]:https://doc.rust-lang.org/nightly/reference/visibility-and-privacy.html 152 | 153 | ```rust,ignored 154 | bitflags! { 155 | struct PrivateFlags: u8 { 156 | const A = 0b0001; 157 | const B = 0b0010; 158 | } 159 | } 160 | 161 | bitflags! { 162 | pub struct PublicFlags: u8 { 163 | const C = 0b0100; 164 | const D = 0b1000; 165 | } 166 | } 167 | ``` 168 | 169 | 170 | 171 | ## 类型分类符 `$t:ty` 是灵活的 172 | 173 | > Type fragments are flexible (C-MACRO-TY) 174 | 175 | 如果你写的宏接收 [`$t:ty`] 类型 [分类符][fragment-specifier] 作为输入, 176 | 那么输入的内容应该与以下代码一起使用: 177 | 178 | - 原生类型: `u8`, `&str` 179 | - 相对路径: `m::Data` 180 | - 绝对路径: `::base::Data` 181 | - 上级相对路径: `super::Data` 182 | - 泛型: `Vec` 183 | 184 | 下面这个例子展示了错误的宏: 185 | 这个宏能在很好地与原生类型、绝对路径一起使用, 186 | 但不能和相对路径一起使用。 187 | 188 | ```rust 189 | macro_rules! broken { 190 | ($m:ident => $t:ty) => { 191 | pub mod $m { 192 | pub struct Wrapper($t); 193 | } 194 | } 195 | } 196 | 197 | broken!(a => u8); // okay 198 | 199 | broken!(b => ::std::marker::PhantomData<()>); // okay 200 | 201 | struct S; 202 | broken!(c => S); // 编译失败: `S` not found in this scope 203 | ``` 204 | 205 | [`$t:ty`]:https://doc.rust-lang.org/nightly/reference/types.html 206 | [fragment-specifier]:https://doc.rust-lang.org/nightly/reference/macros-by-example.html#metavariables 207 | -------------------------------------------------------------------------------- /src/naming.md: -------------------------------------------------------------------------------- 1 | # 命名规范 2 | 3 | 4 | 5 | ## 大小写规范 RFC 430 6 | 7 | > Casing conforms to RFC 430 (C-CASE) 8 | 9 | 基础的命名规范在 [RFC 430] 中有描述。 10 | 11 | 通常,Rust 倾向于在“类型级别”(类型和 trait )的结构中使用 `UpperCamelCase` , 12 | 在“值级别”的结构中使用 `snake_case` 。确切说: 13 | 14 | | Item | 规范 | 15 | | ---- | ---------- | 16 | | Crates | [有争议](https://github.com/rust-lang/api-guidelines/issues/29) [^crate-name] | 17 | | Modules | `snake_case` | 18 | | Types | `UpperCamelCase` | 19 | | Traits | `UpperCamelCase` | 20 | | Enum variants | `UpperCamelCase` | 21 | | Functions | `snake_case` | 22 | | Methods | `snake_case` | 23 | | General constructors | `new` 或者 `with_more_details` | 24 | | Conversion constructors | `from_some_other_type` | 25 | | Macros | `snake_case!` | 26 | | Local variables | `snake_case` | 27 | | Statics | `SCREAMING_SNAKE_CASE` | 28 | | Constants | `SCREAMING_SNAKE_CASE` | 29 | | Type parameters | 简明的 `UpperCamelCase` ,通常使用单个大写字母: `T` | 30 | | Lifetimes | 简短的 `lowercase`,通常使用单个小写字母 `'a`, `'de`, `'src` | 31 | | Features | [有争议](https://github.com/rust-lang/api-guidelines/issues/101) ,但是一般遵照 [C-FEATURE] | 32 | 33 | 在 `UpperCamelCase` 情况下,复合词的首字母缩略词和缩略词算作一个单词: 34 | 使用 `Uuid` 而不使用 `UUID`,使用 `Usize` 而不使用 `USize`, 35 | 使用 `Stdin` 而不使用 `StdIn`。 36 | 在 `snake_case` 情况下,首字母缩略词和缩略词都是小写: `is_xid_start` 。 37 | 38 | 在 `snake_case` 或者 `SCREAMING_SNAKE_CASE` 情况下, 39 | 一个“单词”不应该由单个字母组成——除非这个字母时最后一个“词”: 40 | 使用 `btree_map` 而不使用 `b_tree_map`,使用 `PI_2` 而不使用 `PI2` 。 41 | 42 | Crate 的名称不应该使用 `-rs` 或者 `-rust` 作为后缀或者前缀。 43 | 因为每个 crate 都是 Rust 编写的! 44 | 没必要一直提醒使用者这一点。 45 | 46 | [^crate-name]:译者注:一般是 `snake_case` 或 `kebab-case` ,但只能存在一种形式, 47 | 且实际在 Rust 代码中使用时,后者需使用前者形式。 48 | 如 [`use actix_web`](https://github.com/actix/actix-web) 49 | 50 | 51 | [RFC 430]: https://github.com/rust-lang/rfcs/blob/master/text/0430-finalizing-naming-conventions.md 52 | [C-FEATURE]: #c-feature 53 | 54 | 来自标准库的例子: 55 | 56 | 整个标准库都是这样做的。 57 | 这条原则不难。 58 | 59 | 60 | 61 | ## 遵循 `as_`, `to_`, `into_` 规范 用以特定类型转换 62 | 63 | > Ad-hoc conversions follow `as_`, `to_`, `into_` conventions (C-CONV) 64 | 65 | 应该使用带有以下前缀名称方法来进行特定类型转换: 66 | 67 | | 名称前缀 | 内存代价 | 所有权 | 68 | | ------ | ---- | --------- | 69 | | `as_` | 无代价 | borrowed -\> borrowed | 70 | | `to_` | 代价昂贵 | borrowed -\> borrowed
borrowed -\> owned (非 Copy 类型)
owned -\> owned (Copy 类型) | 71 | | `into_` | 视情况而定 | owned -\> owned (非 Copy 类型) | 72 | 73 | 例如: 74 | 75 | - `as_` 76 | - [`str::as_bytes()`] 77 | 用于查看 UTF-8 字节的 `str` 切片, 78 | 这是无内存代价的(不会产生内存分配)。 79 | 传入值是 `&str` 类型,输出值是 `&[u8]` 类型。 80 | - `to_` 81 | - [`Path::to_str`] 82 | 对操作系统路径进行 UTF-8 字节检查,需要很大花销。 83 | 虽然输入和输出都是借用,但是这个方法对运行时产生不容忽视的代价, 84 | 所以不应使用 `as_str` 名称。 85 | - [`str::to_lowercase()`] 86 | 生成正确的 Unicode 小写字符, 87 | 涉及遍历字符串的字符,可能需要分配内存。 88 | 输入值是 `&str` 类型,输出值是 `String` 类型。 89 | - [`f64::to_radians()`] 90 | 把浮点数的角度制转换成弧度制。 91 | 输入和输出都是 `f64` 。没必要传入 `&f64` ,因为复制 `f64` 花销很小。 92 | 但是使用 `into_radians` 名称就会具有误导性,因为输入数据没有被消耗。 93 | - `into_` 94 | - [`String::into_bytes()`] 95 | 从 `String` 提取出背后的 `Vec` 数据,这是无代价的。 96 | 它转移了 `String` 的所有权,然后返回具有所有权的 `Vec` 。 97 | - [`BufReader::into_inner()`] 98 | 转移了 buffered reader 的所有权,取出其背后的 reader ,这是无代价的。 99 | 存于缓冲区的数据被丢弃了。 100 | - [`BufWriter::into_inner()`] 101 | 转移了 buffered writer 的所有权,取出其背后的 writer ,这可能以很大的代价刷新所有缓存数据。 102 | 103 | [`str::as_bytes()`]: https://doc.rust-lang.org/std/primitive.str.html#method.as_bytes 104 | [`Path::to_str`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.to_str 105 | [`str::to_lowercase()`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase 106 | [`f64::to_radians()`]: https://doc.rust-lang.org/std/primitive.f64.html#method.to_radians 107 | [`String::into_bytes()`]: https://doc.rust-lang.org/std/string/struct.String.html#method.into_bytes 108 | [`BufReader::into_inner()`]: https://doc.rust-lang.org/std/io/struct.BufReader.html#method.into_inner 109 | [`BufWriter::into_inner()`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html#method.into_inner 110 | 111 | 以 `as_` 和 `into_` 作为前缀的类型转换通常 *减少了抽象* , 112 | 要么是查看背后的数据 ( `as` ) ,要么是分解 (deconstructe) 背后的数据 ( `into` ) 。 113 | 相对地,以 `to_` 作为前缀的类型转换处于同一个抽象层次, 114 | 但是做了更多的工作。 115 | 116 | 当一个类型用更高级别的语义 (higher-level semantics) 封装 (wraps) 一个与之有关的值时, 117 | 应该使用 `into_inner()` 方法名来取出被封装的值。 118 | 这适用于以下封装器: 119 | 读取缓存 ([`BufReader`]) 、编码或解码 ([`GzDecoder`]) 、取出原子 ([`AtomicBool`]) 、 120 | 或者任何相似的语义 ([`BufWriter`])。 121 | 122 | [`BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html#method.into_inner 123 | [`GzDecoder`]: https://docs.rs/flate2/1.0.20/flate2/read/struct.GzDecoder.html#method.into_inner 124 | [`AtomicBool`]: https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.into_inner 125 | [`BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html#method.into_inner 126 | 127 | 如果类型转换方法返回的类型具有 `mut` 标识符, 128 | 那么这个方法的名称应如同返回类型组成部分的顺序那样,带有 `mut` 名。 129 | 比如 [`Vec::as_mut_slice`] 返回 `mut slice` 类型,这个方法的功能正如其名称所述, 130 | 所以这个名称优于 `as_slice_mut` 。 131 | 132 | [`Vec::as_mut_slice`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.as_mut_slice 133 | 134 | ```rust.ignored 135 | // Return type is a mut slice. 136 | fn as_mut_slice(&mut self) -> &mut [T]; 137 | ``` 138 | 139 | 更多来自标准库的例子: 140 | 141 | - [`Result::as_ref`](https://doc.rust-lang.org/std/result/enum.Result.html#method.as_ref) 142 | - [`RefCell::as_ptr`](https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.as_ptr) 143 | - [`slice::to_vec`](https://doc.rust-lang.org/std/primitive.slice.html#method.to_vec) 144 | - [`Option::into_iter`](https://doc.rust-lang.org/std/option/enum.Option.html#method.into_iter) 145 | 146 | 147 | 148 | ## getter 命名规范 149 | 150 | > Getter names follow Rust convention (C-GETTER) 151 | 152 | 在 Rust 代码中,getter 方法(用来访问或者获取数据的方法) 153 | 并不使用 `get_` 前缀,但是也有一些例外。 154 | 155 | ```rust,ignored 156 | pub struct S { 157 | first: First, 158 | second: Second, 159 | } 160 | 161 | impl S { 162 | // Not `get_first`. 163 | pub fn first(&self) -> &First { 164 | &self.first 165 | } 166 | 167 | // Not `get_first_mut`, `get_mut_first`, or `mut_first`. 168 | pub fn first_mut(&mut self) -> &mut First { 169 | &mut self.first 170 | } 171 | } 172 | ``` 173 | 174 | 仅在访问或获得 一个事物 这种显而易见的情况时,使用 `get` 来命名才比较合理。 175 | 比如 [`Cell::get`] 访问其 `Cell` 内容。 176 | 177 | [`Cell::get`]: https://doc.rust-lang.org/std/cell/struct.Cell.html#method.get 178 | 179 | 对于在运行时进行验证(比如边界检查)的 getters , 180 | 考虑给 unsafe 的方法名称增加 `_unchecked` 。 181 | 通常会有以下一些签名: 182 | 183 | ```rust,ignored 184 | fn get(&self, index: K) -> Option<&V>; 185 | fn get_mut(&mut self, index: K) -> Option<&mut V>; 186 | unsafe fn get_unchecked(&self, index: K) -> &V; 187 | unsafe fn get_unchecked_mut(&mut self, index: K) -> &mut V; 188 | ``` 189 | 190 | getter 和类型转换 (conversion | [C-CONV](#c-conv)) 之间的区别很小, 191 | 大部分时候不那么清晰可辨。 192 | 比如 [`TempDir::path`] 可以被理解为临时目录的文件系统路径的 getter , 193 | 而 [`TempDir::into_path`] 负责把删除临时目录时转换的数据传给调用者。 194 | 因为 `path` 方法是一个 getter ,如果用 `get_path` 或者 `as_path` 就不对了。 195 | 196 | [`TempDir::path`]: https://docs.rs/tempdir/0.3.7/tempdir/struct.TempDir.html#method.path 197 | [`TempDir::into_path`]: https://docs.rs/tempdir/0.3.7/tempdir/struct.TempDir.html#method.into_path 198 | 199 | 来自标准库的例子: 200 | 201 | - [`std::io::Cursor::get_mut`](https://doc.rust-lang.org/std/io/struct.Cursor.html#method.get_mut) 202 | - [`std::ptr::Unique::get_mut`](https://doc.rust-lang.org/std/ptr/struct.Unique.html#method.get_mut) 203 | - [`std::sync::PoisonError::get_mut`](https://doc.rust-lang.org/std/sync/struct.PoisonError.html#method.get_mut) 204 | - [`std::sync::atomic::AtomicBool::get_mut`](https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.get_mut) 205 | - [`std::collections::hash_map::OccupiedEntry::get_mut`](https://doc.rust-lang.org/std/collections/hash_map/struct.OccupiedEntry.html#method.get_mut) 206 | - [`<[T]>::get_unchecked`](https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked) 207 | 208 | 209 | 210 | ## 遵循 `iter`, `iter_mut`, `into_iter` 规范 用以生成迭代器 211 | 212 | > Methods on collections that produce iterators follow `iter`, `iter_mut`, `into_iter` (C-ITER) 213 | 214 | 参考 [RFC 199] 。 215 | 216 | 对于容纳 `U` 类型的容器 (container) ,其迭代器方法应该这样命名: 217 | 218 | ```rust,ignored 219 | fn iter(&self) -> Iter // Iter implements Iterator 220 | fn iter_mut(&mut self) -> IterMut // IterMut implements Iterator 221 | fn into_iter(self) -> IntoIter // IntoIter implements Iterator 222 | ``` 223 | 224 | 这条原则适用于具有同质概念的集合型 225 | (conceptually homogeneous collections[^conceptually-homogeneous]) 数据结构。 226 | 227 | 有一个反例: `str` 类型是有效 UTF-8 字节的切片, 228 | 概念上与输出同类型的集合略有不同[^str-iter], 229 | 所以 `str` 没有提供一组 `iter`/`iter_mut`/`into_iter` 命名的迭代器方法, 230 | 而是提供 [`str::bytes`] 方法来输出字节迭代器、 231 | [`str::chars`] 方法来输出字符迭代器。 232 | 233 | [`str::bytes`]: https://doc.rust-lang.org/std/primitive.str.html#method.bytes 234 | [`str::chars`]: https://doc.rust-lang.org/std/primitive.str.html#method.chars 235 | [RFC 199]: https://github.com/rust-lang/rfcs/blob/master/text/0199-ownership-variants.md 236 | [^conceptually-homogeneous]: 译者注:可以观察到, 237 | 这一组方法都输出相同的迭代器关联类型 `U` , 238 | 区别仅仅在于操作数据的所有权不同, `iter` 不可变借用、 `iter_mut` 可变借用、 239 | `into_iter` 独立的所有权,所以认为概念上类型相同。 240 | 241 | [^str-iter]: 译者注:因为 `str` 需要转化为 **两种** 与之相关的同样重要的类型。 242 | 这个“反例”是 [下面一条原则](#c-iter-ty) 的典型案例。 243 | 244 | 来自标准库的例子: 245 | 246 | - [`Vec::iter`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter) 247 | - [`Vec::iter_mut`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter_mut) 248 | - [`Vec::into_iter`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_iter) 249 | - [`BTreeMap::iter`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html#method.iter) 250 | - [`BTreeMap::iter_mut`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html#method.iter_mut) 251 | 252 | 253 | 254 | ## 生成迭代器的方法与迭代器类型同名 255 | 256 | > Iterator type names match the methods that produce them (C-ITER-TY) 257 | 258 | 名为 `into_iter()` 的方法应该返回 `IntoIter` 类型的迭代器, 259 | 类似地,不同名称的方法应返回对应类型的迭代器。[^iter-ty] 260 | 261 | 当用在具有模块路径前缀的自定义迭代器类型时, 262 | 这条原则就十分合理。比如 [`vec::IntoIter`] 。 263 | 264 | [`vec::IntoIter`]: https://doc.rust-lang.org/std/vec/struct.IntoIter.html 265 | [^iter-ty]: 译者注:这可能与 [上面一条原则](#c-iter) 看似矛盾,实则相辅相成。\ 266 | 可以把迭代器类型分成两类:与 `Iter` 概念上同质的迭代器; 267 | 基于 `Iter` 型迭代器二度封装的迭代器。\ 268 | 前者命名规则和例子参考 [上一条原则](#c-iter) ; 269 | 后者的典型例子是 `std::collections` 模块下的 270 | [`BTreeMap`](https://doc.rust-lang.org/std/collections/struct.BTreeMap.html) 、 271 | [`HashMap`](https://doc.rust-lang.org/std/collections/struct.HashMap.html) , 272 | 它们各自定义了两种迭代器,二度封装的迭代器用于迭代 Keys 和 Values 。 273 | [`str::bytes`] 和 [`str::chars`] 274 | 也是基于 [`slice`](https://doc.rust-lang.org/std/slice/index.html#structs-1) 的 275 | [`Iter`](https://doc.rust-lang.org/std/slice/struct.Iter.html) 276 | 或 [`IterMut`](https://doc.rust-lang.org/std/slice/struct.IterMut.html) 封装的。 277 | 278 | 279 | 280 | 来自标准库的例子: 281 | 282 | * [`Vec::iter`] 返回 [`Iter`][slice::Iter] 283 | * [`Vec::iter_mut`] 返回 [`IterMut`][slice::IterMut] 284 | * [`Vec::into_iter`] 返回 [`IntoIter`][vec::IntoIter] 285 | * [`BTreeMap::keys`] 返回 [`Keys`][btree_map::Keys] 286 | * [`BTreeMap::values`] 返回 [`Values`][btree_map::Values] 287 | 288 | [`Vec::iter`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter 289 | [slice::Iter]: https://doc.rust-lang.org/std/slice/struct.Iter.html 290 | [`Vec::iter_mut`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter_mut 291 | [slice::IterMut]: https://doc.rust-lang.org/std/slice/struct.IterMut.html 292 | [`Vec::into_iter`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_iter 293 | [vec::IntoIter]: https://doc.rust-lang.org/std/vec/struct.IntoIter.html 294 | [`BTreeMap::keys`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html#method.keys 295 | [btree_map::Keys]: https://doc.rust-lang.org/std/collections/btree_map/struct.Keys.html 296 | [`BTreeMap::values`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html#method.values 297 | [btree_map::Values]: https://doc.rust-lang.org/std/collections/btree_map/struct.Values.html 298 | 299 | 300 | 301 | ## cargo feature 名中不应该有无意义的词 302 | 303 | > Feature names are free of placeholder words (C-FEATURE) 304 | 305 | 给 [Cargo feature] 命名时,不要带有无实际含义的的词语, 306 | 比如无需 `use-abc` 或 `with-abc` —— 直接以 `abc` 命名。 307 | 308 | [Cargo feature]: http://doc.crates.io/manifest.html#the-features-section 309 | 310 | 这条原则经常出现在对 Rust 标准库进行 [可选依赖][optional-dependency] 配置的 crate 上。 311 | 最简洁且正确的做法是: 312 | 313 | ```toml 314 | # In Cargo.toml 315 | 316 | [features] 317 | default = ["std"] 318 | std = [] 319 | ``` 320 | 321 | ```rust,ignored 322 | // In lib.rs 323 | 324 | #![cfg_attr(not(feature = "std"), no_std)] 325 | ``` 326 | 327 | 这个例子中,不要给 feature 取 `use-std` 或者 `with-std` 或者除 `std` 之外另取名字。 328 | feature 应与 Cargo 在推断可选依赖时隐含的 features 具有一致的名字。 329 | 假如 `x` crate 对 Serde 和 标准库具有可选依赖关系: 330 | 331 | ```toml 332 | [package] 333 | name = "x" 334 | version = "0.1.0" 335 | 336 | [features] 337 | std = ["serde/std"] 338 | 339 | [dependencies] 340 | serde = { version = "1.0", optional = true } 341 | ``` 342 | 343 | 当我们使用 `x` crate 时,可以使用 `features = ["serde"]` 开启 Serde 依赖。 344 | 类似地,也可以使用 `features = ["std"]` 开启标准库依赖。 345 | Cargo 推断的隐含的 features 应该叫做 `serde` , 346 | 而不是 `use-serde` 或者 `with-serde` 。 347 | 348 | 与之相关的是, Cargo 要求 features 应该是附加的, 349 | 所以像 `no-abc` 的 feature 命名实际上并不正确。 350 | 351 | [optional-dependency]:https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies 352 | 353 | 354 | ## 词性顺序一致 355 | 356 | > Names use a consistent word order (C-WORD-ORDER) 357 | 358 | 以下是来自标准库的处理错误的一些类型: 359 | 360 | - [`JoinPathsError`](https://doc.rust-lang.org/std/env/struct.JoinPathsError.html) 361 | - [`ParseBoolError`](https://doc.rust-lang.org/std/str/struct.ParseBoolError.html) 362 | - [`ParseCharError`](https://doc.rust-lang.org/std/char/struct.ParseCharError.html) 363 | - [`ParseFloatError`](https://doc.rust-lang.org/std/num/struct.ParseFloatError.html) 364 | - [`ParseIntError`](https://doc.rust-lang.org/std/num/struct.ParseIntError.html) 365 | - [`RecvTimeoutError`](https://doc.rust-lang.org/std/sync/mpsc/enum.RecvTimeoutError.html) 366 | - [`StripPrefixError`](https://doc.rust-lang.org/std/path/struct.StripPrefixError.html) 367 | 368 | 369 | 这些名称都按照 **动词-宾语-error** 的单词顺序。 370 | 如果增加“解析地址错误”类型,为了保持词性一致, 371 | 应该使用 `ParseAddrError` 名称,而不是 `AddrParseError` 名称。 372 | 373 | 具体选择什么样的词性顺序并不重要, 374 | 但务必在一个 crate 里注意要保持这样的顺序; 375 | 若提供与标准库中相似功能的东西时, 376 | 也要与标准库名称的词性顺序一致。 377 | -------------------------------------------------------------------------------- /src/necessities.md: -------------------------------------------------------------------------------- 1 | # 必要项 2 | 3 | 4 | 5 | ## 稳定版 crate 必须具有稳定的公有依赖 6 | 7 | > Public dependencies of a stable crate are stable (C-STABLE) 8 | 9 | 一个稳定版 (stable) 的 crate (版本号>=1.0.0) ,其所有的公有依赖都必须是稳定的。 10 | 11 | 公有依赖 (public dependencies) 指当前 crate 使用的公有 API 所来自的 crate 。 12 | 13 | ```rust,ignored 14 | pub fn do_my_thing(arg: other_crate::TheirThing) { /* ... */ } 15 | ``` 16 | 17 | 对于一个 crate 定义这样的函数,如果 `other_crate` 不是稳定的话, 18 | 那么这个 crate 就不是稳定的。 19 | 20 | 要小心,公有依赖可能在不经意之间出现在意料之外的地方。 21 | 22 | ```rust,ignored 23 | pub struct Error { 24 | private: ErrorImpl, 25 | } 26 | 27 | enum ErrorImpl { 28 | Io(io::Error), 29 | // 这没问题,即使 `other_crate` 不是稳定的,因为 `ErrorImpl` 是私有的 30 | Dep(other_crate::Error), 31 | } 32 | 33 | // 不!这让可能不稳定的 `other_crate` 暴露在当前 crate 的公共 API 里 34 | impl From for Error { 35 | fn from(err: other_crate::Error) -> Self { 36 | Error { private: ErrorImpl::Dep(err) } 37 | } 38 | } 39 | ``` 40 | 41 | 42 | 43 | ## crate 及其依赖必须有许可证 44 | 45 | > Crate and its dependencies have a permissive license (C-PERMISSIVE) 46 | 47 | Rust 项目组编写的软件都是 [MIT] 和 [Apache 2.0] 双重许可的。 48 | 如果 crates 需要最大程度地与 Rust 生态兼容,那么建议也这么做。 49 | 下面谈谈选择其他的许可。 50 | 51 | 这里不会详细解释 Rust 版权, [Rust FAQ] 谈到了一小部分。 52 | 这些原则涉及到 Rust 能否被通用的问题, 53 | 在选择许可方面说得不多。 54 | 55 | [MIT]: https://github.com/rust-lang/rust/blob/master/LICENSE-MIT 56 | [Apache 2.0]: https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE 57 | [Rust FAQ]: https://github.com/dtolnay/rust-faq#why-a-dual-mitasl2-license 58 | 59 | 在 `Cargo.toml` 文件的 `license` 字段填上你的项目适用的许可证。 60 | 61 | ```toml 62 | [package] 63 | name = "..." 64 | version = "..." 65 | authors = ["..."] 66 | license = "MIT OR Apache-2.0" 67 | ``` 68 | 69 | 在仓库的根目录添加 `LICENSE-APACHE` 和 `LICENSE-MIT` 文件, 70 | 文件里面涵盖许可的正文。 71 | 你可以在 choosealicense.com 获得这些许可, 72 | 比如 [Apache-2.0](https://choosealicense.com/licenses/apache-2.0/) 73 | 和 [MIT](https://choosealicense.com/licenses/mit/) 。 74 | 75 | 然后在 README.md 文件的最后写上: 76 | 77 | ``` 78 | ## License 79 | 80 | Licensed under either of 81 | 82 | * Apache License, Version 2.0 83 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 84 | * MIT license 85 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 86 | 87 | at your option. 88 | 89 | ## Contribution 90 | 91 | Unless you explicitly state otherwise, any contribution intentionally submitted 92 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 93 | dual licensed as above, without any additional terms or conditions. 94 | ``` 95 | 96 | 除了 MIT/Apache-2.0 双重许可之外, 97 | 另一种 Rust crate 作者们常用的许可声明方式是使用单独的 MIT 或者 BSD 许可。 98 | BSD 许可方式完全与 Rust 的 MIT 许可方式兼容, 99 | 因为它对 MIT 许可做了最小程度的限制。 100 | 101 | 不建议 想要完全兼容 Rust 许可的 crates 仅仅使用 Apache 许可证。 102 | 虽然 Apache 许可证比 MIT 和 BSD 更严格, 103 | 但是在某些场景下会打击或者打消使用这些 cates 的念头, 104 | 所以只使用 Apache 许可证 的软件 在大多数 Rust 技术栈能使用的地方 无法被使用。 105 | 106 | 一个 crate 依赖的许可证可能影响和限制 crate 自身的分发, 107 | 所以 自由许可 的 crate 通常应仅依赖 自由许可 的 crates 。 108 | -------------------------------------------------------------------------------- /src/predictability.md: -------------------------------------------------------------------------------- 1 | # 可预测 2 | 3 | 4 | 5 | ## 智能指针不增加固有方法 6 | 7 | > Smart pointers do not add inherent methods (C-SMART-PTR) 8 | 9 | 举例来说,以下代码说明了为什么 [`Box::into_raw`] 是这样定义的: 10 | 11 | [`Box::into_raw`]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.into_raw 12 | 13 | ```rust,ignored 14 | impl Box where T: ?Sized { 15 | fn into_raw(b: Box) -> *mut T { /* ... */ } 16 | } 17 | 18 | let boxed_str: Box = /* ... */; 19 | let ptr = Box::into_raw(boxed_str); 20 | ``` 21 | 22 | 如果把 `into_raw` 定义为固有方法[^inherent-method] 23 | ([inherent method][inherent methods]) , 24 | 那么在调用它的时候,很难分清这个方法是来自于 `Box` 的 25 | 还是 来自于 `T` 的。[^C-SMART-PTR] 26 | 27 | ```rust,ignored 28 | impl Box where T: ?Sized { 29 | // 别这样定义 30 | fn into_raw(self) -> *mut T { /* ... */ } 31 | } 32 | 33 | let boxed_str: Box = /* ... */; 34 | 35 | // 这个方法来自于 `str` ,通过智能指针的 `Deref` 做到的 36 | boxed_str.chars() 37 | 38 | // 但这个方法是来自于 `Box` 的...吗?(需要去翻阅文档验证) 39 | boxed_str.into_raw() 40 | ``` 41 | 42 | [^inherent-method]: 译者注:Rust 里没有继承 (inherence) 的概念, 43 | 可参阅 [Rust Book: inherence] 一节。\ 44 | 但是有 **inherent** 的概念,比如 [inherent methods] 、[inherent implementations] , 45 | 一般对这个形容词翻译为 “[固有(的)][固有的]” 。\ 46 | 然而 inherent methods = [associated (or staic) functions] + [methods] , 47 | 所以我认为这句的 inherent method 应该是指 [method][methods] (带 `self` 参数的函数)。 48 | 49 | [^C-SMART-PTR]: 译者注:这个原则是提醒大家,智能指针的固有方法一般以关联函数形式定义, 50 | 但也不是绝对没有方法,比如 [`RC::downcast`] 的参数就是 `self` 。 51 | 52 | 53 | [`RC::downcast`]: http://129.28.186.100/rust-docs/rust/html/std/rc/struct.Rc.html#method.downcast 54 | [methods]: https://doc.rust-lang.org/nightly/reference/items/associated-items.html#methods 55 | [associated (or staic) functions]: https://doc.rust-lang.org/nightly/reference/items/associated-items.html#associated-functions-and-methods 56 | [inherent methods]: http://doc.rust-lang.org/nightly/reference/glossary.html#inherent-method 57 | [inherent implementations]: https://doc.rust-lang.org/nightly/reference/items/implementations.html#inherent-implementations 58 | [固有的]: https://minstrel1977.gitee.io/rust-reference/items/implementations.html#inherent-implementations 59 | [Rust Book: inherence]: https://doc.rust-lang.org/book/ch17-01-what-is-oo.html?highlight=inherence#inheritance-as-a-type-system-and-as-code-sharing 60 | 61 | 62 | ## 类型转换的重点应放在涉及类型中最明确的类型上 63 | 64 | > Conversions live on the most specific type involved (C-CONV-SPECIFIC) 65 | 66 | 在拿不准怎么转换类型的时候,相比于使用 `from_` ,建议优先使用 67 | `to_` / `as_` / `into_` 。因为后者更人性化, 68 | 而且可与其他方法一起被链式调用。 69 | 70 | 对于转换涉及的两种类型,很多时候 其中一种类型更清晰明确: 71 | 这种类型 有某些额外的不变式 (invariant) 或 表现出其他类型不具备的特点。 72 | 比如 [`str`] 就比 `&[u8]` 更明确,因为前者是 UTF-8 编码的字节序列。 73 | 74 | [`str`]: https://doc.rust-lang.org/std/primitive.str.html 75 | 76 | 转换应该重点放在所涉及类型中更明确的类型上。 77 | 所以 `str` 提供了 [`as_bytes`] 方法和 [`from_utf8`] 构造函数, 78 | 从而实现从 `str` 转换成 `&[u8]` 值、从 `&[u8]` 转换成 `str` 。 79 | 除了更直观,这种做法避免用无数转换方法 污染 `&[u8]` 这样的具体类型。 80 | 81 | 82 | [`as_bytes`]: https://doc.rust-lang.org/std/primitive.str.html#method.as_bytes 83 | [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html 84 | 85 | 86 | 87 | ## 有清楚接收者的函数应写成方法的形式 88 | 89 | > Functions with a clear receiver are methods (C-METHOD) 90 | 91 | 如果一个函数操作与具体对象之间具有清楚而密切的关系,那么建议这样做: 92 | 93 | ```rust,ignored 94 | impl Foo { 95 | pub fn frob(&self, w: widget) { /* ... */ } 96 | } 97 | ``` 98 | 99 | 而不建议这样做: 100 | 101 | ```rust,ignored 102 | pub fn frob(foo: &Foo, w: widget) { /* ... */ } 103 | ``` 104 | 105 | 方法 ([methods]) 比函数有着巨大的优势: 106 | 107 | * 方法可以直接被调用,无需导入或者验证资格:你需要的就只是一个合适的类型的值。 108 | * 调用方法会自动借用(包括可变借用)[^auto-refer]。 109 | * 方法可以轻松回答 “如何处理具有类型 `T` 的值” 的问题(在 rustdoc 中尤其有用)。 110 | * 方法提供的 `self` 标注在传达所有权关系上更加简明和清晰。 111 | 112 | [^auto-refer]: 译者注:如果你不明白这句话,可以阅读 113 | [Rust Book: Where’s the -> Operator?](https://doc.rust-lang.org/book/ch05-03-method-syntax.html#wheres-the---operator) 114 | 115 | 116 | ## 函数不该把返回值作为其参数 117 | 118 | > Functions do not take out-parameters (C-NO-OUT) 119 | 120 | 返回多个 `Bar` 类型的值,建议写成[^C-NO-OUT]: 121 | 122 | ```rust,ignored 123 | fn foo() -> (Bar, Bar) 124 | ``` 125 | 126 | 而不写成: 127 | 128 | ```rust,ignored 129 | fn foo(output: &mut Bar) -> Bar 130 | ``` 131 | 132 | 返回元组和结构体这样的复合类型是被编译器高效处理的,而且不需要堆分配。 133 | 如果函数需要返回多个值,那么应该借助这些复合类型。 134 | 135 | 一个重要的反例[^C-NO-OUT2]: 136 | 函数的目的是修改已有数据的值,那么不适用于这条原则。 137 | 比如重新使用缓冲数据: 138 | 139 | ```rust,ignored 140 | fn read(&mut self, buf: &mut [u8]) -> io::Result 141 | ``` 142 | 143 | [^C-NO-OUT]: 译者注:这个例子大概是说,如果函数返回的值与外界没什么关系, 144 | 就无需传入函数。比如函数返回完全在其内部处理产生的 `&'static str` , 145 | 那么就不需要给函数传入 `&mut str` 。 146 | 147 | [^C-NO-OUT2]: 译者注:再举一个常见的反例,外部预先定义空字符串,然后传入函数, 148 | 函数通过可变引用已经处理了输入,再返回这个字符串的话就是不必要的了。 149 | 所以这个原则的目的可能是让读者处理函数输入与输出的时候,弄清楚真正的意图, 150 | 你需要所有权的数据还是修改数据,从而采用高效的做法。 151 | 152 | 153 | ## 重载运算符不足为奇 154 | 155 | > Operator overloads are unsurprising (C-OVERLOAD) 156 | 157 | 内置的运算符语法( `*` 、 `|` 等等)可以通过实现 [`std::ops`] 里的 trait 158 | 使其作用于任意类型。这些运算符带有明确的目的: 159 | `Mul` trait 只应用于类似于乘法的操作 160 | (而且具有某些性质,像可结合性这样的运算性质) , 161 | 其他 trait 也是类似的。 162 | 163 | [`std::ops`]: https://doc.rust-lang.org/std/ops/index.html#traits 164 | 165 | 166 | 167 | ## 只对智能指针实现 `Deref` 和 `DerefMut` trait 168 | 169 | > Only smart pointers implement `Deref` and `DerefMut` (C-DEREF) 170 | 171 | 很多情况下,编译器会隐式使用 `Deref` trait 来与方法做交互。 172 | 与之有关的规则是专门为适应智能指针而设计的, 173 | 所以这个 trait 只能应用于智能指针。 174 | 175 | 来自标准库的例子: 176 | 177 | - [`Box`](https://doc.rust-lang.org/std/boxed/struct.Box.html) 178 | - [`String`](https://doc.rust-lang.org/std/string/struct.String.html) 179 | 是 [`str`](https://doc.rust-lang.org/std/primitive.str.html) 的智能指针 180 | - [`Rc`](https://doc.rust-lang.org/std/rc/struct.Rc.html) 181 | - [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) 182 | - [`Cow<'a, T>`](https://doc.rust-lang.org/std/borrow/enum.Cow.html) 183 | 184 | 185 | 186 | ## 构造函数是静态的、固有的方法 187 | 188 | > Constructors are static, inherent methods (C-CTOR) 189 | 190 | 在 Rust 中,构造函数 (constructor) 是一种惯例。 191 | 关于构造函数,也有各种习惯性做法, 192 | 这些做法之间有着细微的区别。 193 | 194 | 1. 最基础的构造函数是 没有任何参数的 `new` 命名的形式: 195 | 196 | ```rust,ignored 197 | impl Example { 198 | pub fn new() -> Example { /* ... */ } 199 | } 200 | ``` 201 | 202 | 构造函数是某个类型静态的(不带 `self` 参数)、固有的方法, 203 | 用来构造(或者说 实例化 )该类型数据。 204 | 构造函数通常与完整导入类型名称一起工作,从而让构造过程清晰而简明。 205 | 206 | ```rust,ignored 207 | use example::Example; 208 | 209 | // Construct a new Example. 210 | let ex = Example::new(); 211 | ``` 212 | 213 | `new` 函数一般应该作为实例化类型的基础方法。 214 | 有时像上面一样不带有任何参数,有时也会像 [`Box::new`] 需要放入`Box` 的值那样 215 | 那样带有参数。 216 | 217 | 2. 有些类型的构造函数,尤其 I/O 资源类型里的绝大多数类型, 218 | 习惯上给构造函数取区分性很强的名字。\ 219 | 比如 [`File::open`] 、 [`Mmap::open`] 、 220 | [`TcpStream::connect`] 和 [`UdpSocket::bind`] 。 221 | 在专门领域内取这些名字是合适的。 222 | 223 | 3. 有时会有多种方式构造一个类型。这种情况下,像 [`Mmap::open_with_offset`] 那样 224 | 使用 `_with_foo` 后缀作为二级构造函数是很常见的。\ 225 | 但是如果你的类型需要各种初始化配置 (construction options) , 226 | 那就考虑使用 构造模式 ([builder pattern][C-BUILDER]) 吧。 227 | 228 | 4. 有些构造函数是 “类型转换构造器” (conversion constructors) , 229 | 即 从已有的类型的值生成新的另外一个类型的值。 230 | 这种构造函数的名字往往以 `from_` 开头, 231 | 就像 [`std::io::Error::from_raw_os_error`] 那样。 232 | 注意,虽然这与 [`From`][C-CONV-TRAITS] trait 十分相像, 233 | 但是它们之间有三个不同点: 234 | 235 | - `from_` 构造函数可能是 unsafe 的;而 `From` trait 不可能是 unsafe 的。 236 | 一个例子是 [`Box::from_raw`] 。 237 | - `from_` 构造函数可以接收额外的参数来让源数据转换方式更加清楚,正如 238 | [`u64::from_str_radix`] 。 239 | - `From` trait 只适合源数据类型足够确定输出数据类型编码的情况。 240 | 当输入一大堆位数据,使用 [`u64::from_be`] 或 [`String::from_utf8`] 241 | 这样的类型转换函数才能知道数据的类型。 242 | 243 | [`Box::from_raw`]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from_raw 244 | [`u64::from_str_radix`]: https://doc.rust-lang.org/std/primitive.u64.html#method.from_str_radix 245 | [`u64::from_be`]: https://doc.rust-lang.org/std/primitive.u64.html#method.from_be 246 | [`String::from_utf8`]: https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8 247 | 248 | 5. 注意,同时实现 `Default` trait 和 `new` 构造函数是常见而必要的。 249 | 如果都用在一个类型上,那么它们所做的事情应该一模一样。 250 | 可以根据其中一个来实现另一个。 251 | 252 | [C-BUILDER]: type-safety.html#c-builder 253 | [C-CONV-TRAITS]: interoperability.html#c-conv-traits 254 | 255 | 来自标准库的例子: 256 | 257 | - [`std::io::Error::new`] 常用来构造 IO 错误 258 | - [`std::io::Error::from_raw_os_error`] 259 | 是一个基于操作系统错误代码的转换构造器 260 | - [`Box::new`] 生成新的容器类型,需要一个参数 261 | - [`File::open`] 打开一个文件资源 262 | - [`Mmap::open_with_offset`] 打开映射到内存的文件,支持附加的配置选项 263 | 264 | [`File::open`]: https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.open 265 | [`Mmap::open`]: https://docs.rs/memmap/0.5.2/memmap/struct.Mmap.html#method.open 266 | [`Mmap::open_with_offset`]: https://docs.rs/memmap/0.5.2/memmap/struct.Mmap.html#method.open_with_offset 267 | [`TcpStream::connect`]: https://doc.rust-lang.org/stable/std/net/struct.TcpStream.html#method.connect 268 | [`UdpSocket::bind`]: https://doc.rust-lang.org/stable/std/net/struct.UdpSocket.html#method.bind 269 | [`std::io::Error::new`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.new 270 | [`std::io::Error::from_raw_os_error`]: https://doc.rust-lang.org/std/io/struct.Error.html#method.from_raw_os_error 271 | [`Box::new`]: https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.new 272 | 273 | -------------------------------------------------------------------------------- /src/type-safety.md: -------------------------------------------------------------------------------- 1 | # 类型安全 2 | 3 | 4 | 5 | ## newtype 提供静态的区分功能 6 | 7 | > Newtypes provide static distinctions (C-NEWTYPE) 8 | 9 | newtype 能静态地[^static]区分同一个里层类型不同的含义。 10 | 11 | 比如 一个 `f64` 的值可能用来表示几英里,也可能表示几公里。 12 | 使用 newtype ,就能让我们知道这个值意图表示什么。 13 | 14 | ```rust,ignored 15 | struct Miles(pub f64); 16 | struct Kilometers(pub f64); 17 | 18 | impl Miles { 19 | fn to_kilometers(self) -> Kilometers { /* ... */ } 20 | } 21 | impl Kilometers { 22 | fn to_miles(self) -> Miles { /* ... */ } 23 | } 24 | ``` 25 | 26 | 一旦对这两种类型作区分,我们能静态地确保不把他们混为一谈。 27 | 打个比方,下面这个函数不会意外地会调用 `Kilometers` 类型的值。 28 | 29 | ```rust,ignored 30 | fn are_we_there_yet(distance_travelled: Miles) -> bool { /* ... */ } 31 | ``` 32 | 33 | 编译器会提醒我们需要转换成 `Miles` , 34 | 从而避免了某些 [灾难性的 bugs][catastrophic bugs] 。 35 | 36 | [catastrophic bugs]: http://en.wikipedia.org/wiki/Mars_Climate_Orbiter 37 | 38 | [^static]: 译者注:之所以称之为 *static* / *statically* ,是因为 newtype 39 | 类型一旦被定义就不会发生变化,从而它代表的含义就是固定不变的。 40 | 41 | 42 | ## 参数应使用类型来表明意图 43 | 44 | > Arguments convey meaning through types, not `bool` or `Option` (C-CUSTOM-TYPE) 45 | 46 | 请写这样的代码: 47 | 48 | ```rust,ignored 49 | let w = Widget::new(Small, Round) 50 | ``` 51 | 52 | 而不是这样的: 53 | 54 | ```rust,ignored 55 | let w = Widget::new(true, false) 56 | ``` 57 | 58 | 像 `bool` 、 `u8` 、 `Option` 这样的核心类型有很多可能的含义。 59 | 60 | 所以无论你使用枚举体、结构体还是元组, 61 | 请经过思考定义一种类型来传达含义和 [不变性][invariants]。 62 | 上面这个例子中,如果不看参数的名字,根据 `true` 和 `false` 63 | 你不能马上知道它们是什么意思,但是 `Small` 和 `Round` 就更具有联想性。 64 | 65 | 使用自定义类型让后面要谈到的配置拓展更简单,比如增加一个 `ExtraLarge` 成员。 66 | 67 | 阅读 [newtype 模式][C-NEWTYPE] 来做到以低花销的方式封装现有类型。 68 | 69 | [invariants]: https://doc.rust-lang.org/nightly/reference/subtyping.html#variance 70 | 71 | [C-NEWTYPE]: #c-newtype 72 | 73 | 74 | 75 | ## 用 `bitflags` 来存放一组标志 76 | 77 | > Types for a set of flags are `bitflags`, not enums (C-BITFLAG) 78 | 79 | Rust 支持 `enum` 类型,它可以带有显式的判别式 (discriminants) 。 80 | 81 | ```rust,ignored 82 | enum Color { 83 | Red = 0xff0000, 84 | Green = 0x00ff00, 85 | Blue = 0x0000ff, 86 | } 87 | ``` 88 | 89 | 当枚举类型需要被序列化成整数以便兼容其他系统或语言的时候,自定义判别式是有用的。 90 | 判别式提供了 “类型安全” 的 APIs : 91 | 给函数传入 `Color` 而不是整数,就能保证得到良好形式的输入, 92 | 即使函数把这些 `Color` 输入仍然看作是整数。 93 | 94 | `enum` 允许 API 从其中刚好选择一个成员。 95 | 有时 API 输入存在或需要一组标志 (flags) ,在 C 语言里, 96 | 经常让每个标志 (flag) 对应上一个特定的位 (bit) , 97 | 比如让一个数字代表是 32 位或 64 位。 98 | Rust 的 [`bitflags`] crate 为这种模式提供了类型安全的解决办法。 99 | 100 | [`bitflags`]: https://github.com/bitflags/bitflags 101 | 102 | ```rust,ignored 103 | #[macro_use] 104 | extern crate bitflags; 105 | 106 | bitflags! { 107 | struct Flags: u32 { 108 | const FLAG_A = 0b00000001; 109 | const FLAG_B = 0b00000010; 110 | const FLAG_C = 0b00000100; 111 | } 112 | } 113 | 114 | fn f(settings: Flags) { 115 | if settings.contains(Flags::FLAG_A) { 116 | println!("doing thing A"); 117 | } 118 | if settings.contains(Flags::FLAG_B) { 119 | println!("doing thing B"); 120 | } 121 | if settings.contains(Flags::FLAG_C) { 122 | println!("doing thing C"); 123 | } 124 | } 125 | 126 | fn main() { 127 | f(Flags::FLAG_A | Flags::FLAG_C); 128 | } 129 | ``` 130 | 131 | 132 | 133 | ## 利用构造模式构造复杂的值 134 | 135 | > Builders enable construction of complex values (C-BUILDER) 136 | 137 | 有些数据结构构造起来很复杂,因为构造它们需要: 138 | 139 | * 大量的输入 140 | * 复合类型(比如 slices ) 141 | * 可选择的配置数据 142 | * 在多个可选项中做选择 143 | 144 | 这很容易导致许多带有不同的构造器 (constructors) ,而且每个构造器有很多参数。 145 | 146 | 假设 `T` 是上面描述的一种数据结构,那么考虑给 `T` 引入构造器模式 (builder pattern) 。 147 | 148 | 1. 引入单独的数据类型 `TBuilder` 来增量配置 `T` 的值。 149 | 如果可以的话,选一个更好听的名字:比如 [`Command`] 是 150 | 子进程 [Child][child process] 的构造器、 151 | [`ParseOptions`] 是 [`Url`] 的构造器。 152 | 2. 仅当 `T` 需要数据的时候才应该给构造器的方法传入参数。 153 | 3. 构造器应该提供一套方便的配置方法,包括增量初始化复合型输入(像 slice 一样)。 154 | 这些方法应该返回 `self` 从而允许链式调用。 155 | 4. 构造器应该提供一个或更多的最终方法来实际构造 `T` 。 156 | 157 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 158 | [child process]: https://doc.rust-lang.org/std/process/struct.Child.html 159 | [`Url`]: https://docs.rs/url/1.4.0/url/struct.Url.html 160 | [`ParseOptions`]: https://docs.rs/url/1.4.0/url/struct.ParseOptions.html 161 | 162 | 构造器模式尤其适合于 构造 `T` 时涉及意外的连带后果 的场景, 163 | 比如开始一个任务或开启一个进程。 164 | 165 | Rust 里有两种构造器模式,区别在于处理所有权上: 166 | 167 | ### 非消耗型构造器 168 | 169 | 170 | 有些情况构造最终的 `T` 不需要构造器被消耗掉 (consume) 。 171 | 下面这个例子是 [`std::process::Command`] 的简化版: 172 | 173 | [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 174 | 175 | ```rust,ignored 176 | // 注意:这是一个简化的版本,实际中 Command API 不使用有所有权的 String 177 | 178 | pub struct Command { 179 | program: String, 180 | args: Vec, 181 | cwd: Option, 182 | // etc 183 | } 184 | 185 | impl Command { 186 | pub fn new(program: String) -> Command { 187 | Command { 188 | program: program, 189 | args: Vec::new(), 190 | cwd: None, 191 | } 192 | } 193 | 194 | /// 增加一个命令行参数以传给程序 195 | pub fn arg(&mut self, arg: String) -> &mut Command { 196 | self.args.push(arg); 197 | self 198 | } 199 | 200 | /// 增加多个命令行参数以传给程序 201 | pub fn args(&mut self, args: &[String]) -> &mut Command { 202 | self.args.extend_from_slice(args); 203 | self 204 | } 205 | 206 | /// 设置子进程工作目录 207 | pub fn current_dir(&mut self, dir: String) -> &mut Command { 208 | self.cwd = Some(dir); 209 | self 210 | } 211 | 212 | /// 以子进程方式执行命令,然后返回子进程 213 | pub fn spawn(&self) -> io::Result { 214 | /* ... */ 215 | } 216 | } 217 | ``` 218 | 注意 `spawn` 方法实际上利用构造器的配置来开启一个进程, 219 | 以共享引用的方式使用了构造器, 220 | 因为开启进程无需配置数据的所有权。 221 | 而且用于配置的方法传入和返回 `self` 的可变引用。 222 | 223 | 这种方式的优点: 224 | 整个过程都使用借用,`Command` 能方便地以一行或更复杂的方式使用。 225 | **推荐** 使用这种方式。 226 | 227 | ```rust,ignored 228 | // 用一行进行配置 229 | Command::new("/bin/cat").arg("file.txt").spawn(); 230 | 231 | // 进行复杂的配置 232 | let mut cmd = Command::new("/bin/ls"); 233 | cmd.arg("."); 234 | if size_sorted { 235 | cmd.arg("-S"); 236 | } 237 | cmd.spawn(); 238 | ``` 239 | 240 | ### 消耗型构造器 241 | 242 | 有时候,为了构造出最后的 `T` ,构造器必须传递所有权。 243 | 这意味着必须给方法传入 `self` 而不是 `&self` 。 244 | 245 | ```rust,ignored 246 | impl TaskBuilder { 247 | /// 给将要运行的任务命名 248 | pub fn named(mut self, name: String) -> TaskBuilder { 249 | self.name = Some(name); 250 | self 251 | } 252 | 253 | /// 把本地任务的 stdout 重新定向 254 | pub fn stdout(mut self, stdout: Box) -> TaskBuilder { 255 | self.stdout = Some(stdout); 256 | self 257 | } 258 | 259 | /// 创建和执行新的子任务 260 | pub fn spawn(self, f: F) where F: FnOnce() + Send { 261 | /* ... */ 262 | } 263 | } 264 | ``` 265 | 266 | 在这里, `stdout` 配置涉及到传传递 `io::Write` 的所有权, 267 | 所以必须在构造的时候( `spawn` 方法里面)再传递给任务。 268 | 269 | 当最终的构造器需要所有权时,面临一个基本的抉择: 270 | 271 | * 如果其他的构造方法被传入或者返回 可变借用 `&mut self` , 272 | 那么复杂的配置情况也能很好地工作,但是不可能用一行完成所有配置。 273 | * 如果其他的构造方法被传入或者返回 有所有权的 `self` , 274 | 可以很好地使用一行来进行所有配置,但是在进行复杂配置时就不太方便。 275 | 276 | 遵循 “让简单的事简单完成,困难的事也可能完成” 这句箴言, 277 | 消耗型构造器所有的方法都应该被传入和返回有所有权的 `self` 。 278 | 用户的代码可以像下面这样工作: 279 | 280 | ```rust,ignored 281 | // 用一行进行配置 282 | TaskBuilder::new("my_task").spawn(|| { /* ... */ }); 283 | 284 | // 进行复杂的配置 285 | let mut task = TaskBuilder::new(); 286 | task = task.named("my_task_2"); // 必须重新赋值出去,从而获得所有权 287 | if reroute { 288 | task = task.stdout(mywriter); 289 | } 290 | task.spawn(|| { /* ... */ }); 291 | ``` 292 | 293 | 一行配置的方式像上一个方法那样可以工作, 294 | 因为所有权在每个构造器方法上依次传递, 295 | 直到被 `spawn` 消耗掉。 296 | 然而复杂配置的情况更啰嗦: 297 | 在最后一步需要把构造器重新赋值给一个变量。 298 | -------------------------------------------------------------------------------- /theme/css/chrome.css: -------------------------------------------------------------------------------- 1 | /* CSS for UI elements (a.k.a. chrome) */ 2 | 3 | @import 'variables.css'; 4 | 5 | ::-webkit-scrollbar { 6 | background: var(--bg); 7 | } 8 | ::-webkit-scrollbar-thumb { 9 | background: var(--scrollbar); 10 | } 11 | html { 12 | scrollbar-color: var(--scrollbar) var(--bg); 13 | } 14 | #searchresults a, 15 | .content a:link, 16 | a:visited, 17 | a > .hljs { 18 | color: var(--links); 19 | } 20 | 21 | /* Menu Bar */ 22 | 23 | #menu-bar, 24 | #menu-bar-hover-placeholder { 25 | z-index: 101; 26 | margin: auto calc(0px - var(--page-padding)); 27 | } 28 | #menu-bar { 29 | position: relative; 30 | display: flex; 31 | flex-wrap: wrap; 32 | background-color: var(--bg); 33 | border-bottom-color: var(--bg); 34 | border-bottom-width: 1px; 35 | border-bottom-style: solid; 36 | } 37 | #menu-bar.sticky, 38 | .js #menu-bar-hover-placeholder:hover + #menu-bar, 39 | .js #menu-bar:hover, 40 | .js.sidebar-visible #menu-bar { 41 | position: -webkit-sticky; 42 | position: sticky; 43 | top: 0 !important; 44 | } 45 | #menu-bar-hover-placeholder { 46 | position: sticky; 47 | position: -webkit-sticky; 48 | top: 0; 49 | height: var(--menu-bar-height); 50 | } 51 | #menu-bar.bordered { 52 | border-bottom-color: var(--table-border-color); 53 | } 54 | #menu-bar i, #menu-bar .icon-button { 55 | position: relative; 56 | padding: 0 8px; 57 | z-index: 10; 58 | line-height: var(--menu-bar-height); 59 | cursor: pointer; 60 | transition: color 0.5s; 61 | } 62 | @media only screen and (max-width: 420px) { 63 | #menu-bar i, #menu-bar .icon-button { 64 | padding: 0 5px; 65 | } 66 | } 67 | 68 | .icon-button { 69 | border: none; 70 | background: none; 71 | padding: 0; 72 | color: inherit; 73 | } 74 | .icon-button i { 75 | margin: 0; 76 | } 77 | 78 | .right-buttons { 79 | margin: 0 15px; 80 | } 81 | .right-buttons a { 82 | text-decoration: none; 83 | } 84 | 85 | .left-buttons { 86 | display: flex; 87 | margin: 0 5px; 88 | } 89 | .no-js .left-buttons { 90 | display: none; 91 | } 92 | 93 | .menu-title { 94 | display: inline-block; 95 | font-weight: 200; 96 | font-size: 2.4rem; 97 | line-height: var(--menu-bar-height); 98 | text-align: center; 99 | margin: 0; 100 | flex: 1; 101 | white-space: nowrap; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | } 105 | .js .menu-title { 106 | cursor: pointer; 107 | } 108 | 109 | .menu-bar, 110 | .menu-bar:visited, 111 | .nav-chapters, 112 | .nav-chapters:visited, 113 | .mobile-nav-chapters, 114 | .mobile-nav-chapters:visited, 115 | .menu-bar .icon-button, 116 | .menu-bar a i { 117 | color: var(--icons); 118 | } 119 | 120 | .menu-bar i:hover, 121 | .menu-bar .icon-button:hover, 122 | .nav-chapters:hover, 123 | .mobile-nav-chapters i:hover { 124 | color: var(--icons-hover); 125 | } 126 | 127 | /* Nav Icons */ 128 | 129 | .nav-chapters { 130 | font-size: 2.5em; 131 | text-align: center; 132 | text-decoration: none; 133 | 134 | position: fixed; 135 | top: 0; 136 | bottom: 0; 137 | margin: 0; 138 | max-width: auto; 139 | min-width: auto; 140 | 141 | display: flex; 142 | justify-content: center; 143 | align-content: center; 144 | flex-direction: column; 145 | 146 | transition: color 0.5s, background-color 0.5s; 147 | } 148 | 149 | .nav-chapters:hover { 150 | text-decoration: none; 151 | background-color: var(--theme-hover); 152 | transition: background-color 0.15s, color 0.15s; 153 | } 154 | 155 | .nav-wrapper { 156 | margin-top: 50px; 157 | display: none; 158 | } 159 | 160 | .mobile-nav-chapters { 161 | font-size: 2.5em; 162 | text-align: center; 163 | text-decoration: none; 164 | width: 90px; 165 | border-radius: 5px; 166 | background-color: var(--sidebar-bg); 167 | } 168 | 169 | .previous { 170 | float: left; 171 | } 172 | 173 | .next { 174 | float: right; 175 | right: var(--page-padding); 176 | } 177 | 178 | @media only screen and (max-width: 1080px) { 179 | .nav-wide-wrapper { display: none; } 180 | .nav-wrapper { display: block; } 181 | } 182 | 183 | @media only screen and (max-width: 1380px) { 184 | .sidebar-visible .nav-wide-wrapper { display: none; } 185 | .sidebar-visible .nav-wrapper { display: block; } 186 | } 187 | 188 | /* Inline code */ 189 | 190 | :not(pre) > .hljs { 191 | display: inline; 192 | padding: 0.1em 0.3em; 193 | border-radius: 3px; 194 | } 195 | 196 | :not(pre):not(a) > .hljs { 197 | color: var(--inline-code-color); 198 | overflow-x: initial; 199 | } 200 | 201 | a:hover > .hljs { 202 | text-decoration: underline; 203 | } 204 | 205 | pre { 206 | position: relative; 207 | } 208 | pre > .buttons { 209 | position: absolute; 210 | z-index: 100; 211 | right: 5px; 212 | top: 5px; 213 | 214 | color: var(--sidebar-fg); 215 | cursor: pointer; 216 | } 217 | pre > .buttons :hover { 218 | color: var(--sidebar-active); 219 | } 220 | pre > .buttons i { 221 | margin-left: 8px; 222 | } 223 | pre > .buttons button { 224 | color: inherit; 225 | background: transparent; 226 | border: none; 227 | cursor: inherit; 228 | } 229 | pre > .result { 230 | margin-top: 10px; 231 | } 232 | 233 | /* Search */ 234 | 235 | #searchresults a { 236 | text-decoration: none; 237 | } 238 | 239 | mark { 240 | border-radius: 2px; 241 | padding: 0 3px 1px 3px; 242 | margin: 0 -3px -1px -3px; 243 | background-color: var(--search-mark-bg); 244 | transition: background-color 300ms linear; 245 | cursor: pointer; 246 | } 247 | 248 | mark.fade-out { 249 | background-color: rgba(0,0,0,0) !important; 250 | cursor: auto; 251 | } 252 | 253 | .searchbar-outer { 254 | margin-left: auto; 255 | margin-right: auto; 256 | max-width: var(--content-max-width); 257 | } 258 | 259 | #searchbar { 260 | width: 100%; 261 | margin: 5px auto 0px auto; 262 | padding: 10px 16px; 263 | transition: box-shadow 300ms ease-in-out; 264 | border: 1px solid var(--searchbar-border-color); 265 | border-radius: 3px; 266 | background-color: var(--searchbar-bg); 267 | color: var(--searchbar-fg); 268 | } 269 | #searchbar:focus, 270 | #searchbar.active { 271 | box-shadow: 0 0 3px var(--searchbar-shadow-color); 272 | } 273 | 274 | .searchresults-header { 275 | font-weight: bold; 276 | font-size: 1em; 277 | padding: 18px 0 0 5px; 278 | color: var(--searchresults-header-fg); 279 | } 280 | 281 | .searchresults-outer { 282 | margin-left: auto; 283 | margin-right: auto; 284 | max-width: var(--content-max-width); 285 | border-bottom: 1px dashed var(--searchresults-border-color); 286 | } 287 | 288 | ul#searchresults { 289 | list-style: none; 290 | padding-left: 20px; 291 | } 292 | ul#searchresults li { 293 | margin: 10px 0px; 294 | padding: 2px; 295 | border-radius: 2px; 296 | } 297 | ul#searchresults li.focus { 298 | background-color: var(--searchresults-li-bg); 299 | } 300 | ul#searchresults span.teaser { 301 | display: block; 302 | clear: both; 303 | margin: 5px 0 0 20px; 304 | font-size: 0.8em; 305 | } 306 | ul#searchresults span.teaser em { 307 | font-weight: bold; 308 | font-style: normal; 309 | } 310 | 311 | /* Sidebar */ 312 | 313 | .sidebar { 314 | position: fixed; 315 | left: 0; 316 | top: 0; 317 | bottom: 0; 318 | width: var(--sidebar-width); 319 | font-size: 1em; 320 | box-sizing: border-box; 321 | -webkit-overflow-scrolling: touch; 322 | overscroll-behavior-y: contain; 323 | background-color: var(--sidebar-bg); 324 | color: var(--sidebar-fg); 325 | } 326 | .sidebar-resizing { 327 | -moz-user-select: none; 328 | -webkit-user-select: none; 329 | -ms-user-select: none; 330 | user-select: none; 331 | } 332 | .js:not(.sidebar-resizing) .sidebar { 333 | transition: transform 0.3s; /* Animation: slide away */ 334 | } 335 | .sidebar code { 336 | line-height: 2em; 337 | } 338 | .sidebar .sidebar-scrollbox { 339 | overflow-y: auto; 340 | position: absolute; 341 | top: 0; 342 | bottom: 0; 343 | left: 0; 344 | right: 0; 345 | padding: 10px 10px; 346 | } 347 | .sidebar .sidebar-resize-handle { 348 | position: absolute; 349 | cursor: col-resize; 350 | width: 0; 351 | right: 0; 352 | top: 0; 353 | bottom: 0; 354 | } 355 | .js .sidebar .sidebar-resize-handle { 356 | cursor: col-resize; 357 | width: 5px; 358 | } 359 | .sidebar-hidden .sidebar { 360 | transform: translateX(calc(0px - var(--sidebar-width))); 361 | } 362 | .sidebar::-webkit-scrollbar { 363 | background: var(--sidebar-bg); 364 | } 365 | .sidebar::-webkit-scrollbar-thumb { 366 | background: var(--scrollbar); 367 | } 368 | 369 | .sidebar-visible .page-wrapper { 370 | transform: translateX(var(--sidebar-width)); 371 | } 372 | @media only screen and (min-width: 620px) { 373 | .sidebar-visible .page-wrapper { 374 | transform: none; 375 | margin-left: var(--sidebar-width); 376 | } 377 | } 378 | 379 | .chapter { 380 | list-style: none outside none; 381 | padding-left: 0; 382 | line-height: 2em; 383 | } 384 | 385 | .chapter ol { 386 | width: 100%; 387 | } 388 | 389 | .chapter li { 390 | display: flex; 391 | color: var(--sidebar-non-existant); 392 | } 393 | .chapter li a { 394 | display: block; 395 | padding: 0; 396 | text-decoration: none; 397 | color: var(--sidebar-fg); 398 | } 399 | 400 | .chapter li a:hover { 401 | color: var(--sidebar-active); 402 | } 403 | 404 | .chapter li a.active { 405 | color: var(--sidebar-active); 406 | } 407 | 408 | .chapter li > a.toggle { 409 | cursor: pointer; 410 | display: block; 411 | margin-left: auto; 412 | padding: 0 10px; 413 | user-select: none; 414 | opacity: 0.68; 415 | } 416 | 417 | .chapter li > a.toggle div { 418 | transition: transform 0.5s; 419 | } 420 | 421 | /* collapse the section */ 422 | .chapter li:not(.expanded) + li > ol { 423 | display: none; 424 | } 425 | 426 | .chapter li.chapter-item { 427 | line-height: 1.5em; 428 | margin-top: 0.6em; 429 | } 430 | 431 | .chapter li.expanded > a.toggle div { 432 | transform: rotate(90deg); 433 | } 434 | 435 | .spacer { 436 | width: 100%; 437 | height: 3px; 438 | margin: 5px 0px; 439 | } 440 | .chapter .spacer { 441 | background-color: var(--sidebar-spacer); 442 | } 443 | 444 | @media (-moz-touch-enabled: 1), (pointer: coarse) { 445 | .chapter li a { padding: 5px 0; } 446 | .spacer { margin: 10px 0; } 447 | } 448 | 449 | .section { 450 | list-style: none outside none; 451 | padding-left: 20px; 452 | line-height: 1.5em; 453 | } 454 | 455 | /* Theme Menu Popup */ 456 | 457 | .theme-popup { 458 | position: absolute; 459 | left: 10px; 460 | top: var(--menu-bar-height); 461 | z-index: 1000; 462 | border-radius: 4px; 463 | font-size: 0.7em; 464 | color: var(--fg); 465 | background: var(--theme-popup-bg); 466 | border: 1px solid var(--theme-popup-border); 467 | margin: 0; 468 | padding: 0; 469 | list-style: none; 470 | display: none; 471 | } 472 | .theme-popup .default { 473 | color: var(--icons); 474 | } 475 | .theme-popup .theme { 476 | width: 100%; 477 | border: 0; 478 | margin: 0; 479 | padding: 2px 10px; 480 | line-height: 25px; 481 | white-space: nowrap; 482 | text-align: left; 483 | cursor: pointer; 484 | color: inherit; 485 | background: inherit; 486 | font-size: inherit; 487 | } 488 | .theme-popup .theme:hover { 489 | background-color: var(--theme-hover); 490 | } 491 | .theme-popup .theme:hover:first-child, 492 | .theme-popup .theme:hover:last-child { 493 | border-top-left-radius: inherit; 494 | border-top-right-radius: inherit; 495 | } 496 | -------------------------------------------------------------------------------- /theme/css/general.css: -------------------------------------------------------------------------------- 1 | /* Base styles and content styles */ 2 | 3 | @import 'variables.css'; 4 | 5 | :root { 6 | /* Browser default font-size is 16px, this way 1 rem = 10px */ 7 | font-size: 70%; 8 | } 9 | 10 | html { 11 | font-family: "Open Sans", sans-serif; 12 | color: var(--fg); 13 | background-color: var(--bg); 14 | 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: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important; 25 | font-size: 0.9em; /* please adjust the ace font size accordingly in editor.js */ 26 | } 27 | 28 | /* Don't change font size in headers. */ 29 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 30 | font-size: unset; 31 | } 32 | 33 | .left { float: left; } 34 | .right { float: right; } 35 | .boring { opacity: 0.6; } 36 | .hide-boring .boring { display: none; } 37 | .hidden { display: none !important; } 38 | 39 | h2, h3 { margin-top: 2.5em; } 40 | h4, h5 { margin-top: 2em; } 41 | 42 | .header + .header h3, 43 | .header + .header h4, 44 | .header + .header h5 { 45 | margin-top: 1em; 46 | } 47 | 48 | h1:target::before, 49 | h2:target::before, 50 | h3:target::before, 51 | h4:target::before, 52 | h5:target::before, 53 | h6:target::before { 54 | display: inline-block; 55 | content: "»"; 56 | margin-left: -30px; 57 | width: 30px; 58 | } 59 | 60 | /* This is broken on Safari as of version 14, but is fixed 61 | in Safari Technology Preview 117 which I think will be Safari 14.2. 62 | https://bugs.webkit.org/show_bug.cgi?id=218076 63 | */ 64 | :target { 65 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); 66 | } 67 | 68 | .page { 69 | outline: 0; 70 | padding: 0 var(--page-padding); 71 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ 72 | } 73 | .page-wrapper { 74 | box-sizing: border-box; 75 | } 76 | .js:not(.sidebar-resizing) .page-wrapper { 77 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ 78 | } 79 | 80 | .content { 81 | overflow-y: auto; 82 | padding: 0 10px; 83 | padding-bottom: 50px; 84 | } 85 | .content main { 86 | margin-left: 2%; 87 | margin-right: 2%; 88 | max-width: var(--content-max-width); 89 | } 90 | .content p { line-height: 1.45em; } 91 | .content ol { line-height: 1.45em; } 92 | .content ul { line-height: 1.45em; } 93 | .content a { text-decoration: none; } 94 | .content a:hover { text-decoration: underline; } 95 | .content img, .content video { max-width: 100%; } 96 | .content .header:link, 97 | .content .header:visited { 98 | color: var(--fg); 99 | } 100 | .content .header:link, 101 | .content .header:visited:hover { 102 | text-decoration: none; 103 | } 104 | 105 | table { 106 | margin: 0 auto; 107 | border-collapse: collapse; 108 | } 109 | table td { 110 | padding: 3px 20px; 111 | border: 1px var(--table-border-color) solid; 112 | } 113 | table thead { 114 | background: var(--table-header-bg); 115 | } 116 | table thead td { 117 | font-weight: 700; 118 | border: none; 119 | } 120 | table thead th { 121 | padding: 3px 20px; 122 | } 123 | table thead tr { 124 | border: 1px var(--table-header-bg) solid; 125 | } 126 | /* Alternate background colors for rows */ 127 | table tbody tr:nth-child(2n) { 128 | background: var(--table-alternate-bg); 129 | } 130 | 131 | 132 | blockquote { 133 | margin: 20px 0; 134 | padding: 0 20px; 135 | color: var(--fg); 136 | background-color: var(--quote-bg); 137 | border-top: .1em solid var(--quote-border); 138 | border-bottom: .1em solid var(--quote-border); 139 | } 140 | 141 | 142 | :not(.footnote-definition) + .footnote-definition, 143 | .footnote-definition + :not(.footnote-definition) { 144 | margin-top: 2em; 145 | } 146 | .footnote-definition { 147 | font-size: 0.9em; 148 | margin: 0.5em 0; 149 | } 150 | .footnote-definition p { 151 | display: inline; 152 | } 153 | 154 | .tooltiptext { 155 | position: absolute; 156 | visibility: hidden; 157 | color: #fff; 158 | background-color: #333; 159 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ 160 | left: -8px; /* Half of the width of the icon */ 161 | top: -35px; 162 | font-size: 0.8em; 163 | text-align: center; 164 | border-radius: 6px; 165 | padding: 5px 8px; 166 | margin: 5px; 167 | z-index: 1000; 168 | } 169 | .tooltipped .tooltiptext { 170 | visibility: visible; 171 | } 172 | 173 | .chapter li.part-title { 174 | color: var(--sidebar-fg); 175 | margin: 5px 0px; 176 | font-weight: bold; 177 | } 178 | -------------------------------------------------------------------------------- /theme/css/variables.css: -------------------------------------------------------------------------------- 1 | 2 | /* Globals */ 3 | 4 | :root { 5 | --sidebar-width: 120px; 6 | --page-padding: 15px; 7 | --content-max-width: 80%; 8 | --menu-bar-height: 40px; 9 | --pagetoc-width: 15%; 10 | --pagetoc-fontsize: 14.5px; 11 | } 12 | 13 | @media only screen and (max-width:1439px) { 14 | :root{ 15 | --content-max-width: 98%; 16 | } 17 | } 18 | 19 | @media only screen and (max-width:1439px) { 20 | :root{ 21 | --content-max-width: 98%; 22 | } 23 | } 24 | 25 | @media only screen and (max-width:1439px) { 26 | :root{ 27 | --content-max-width: 98%; 28 | } 29 | } 30 | 31 | @media only screen and (max-width:1439px) { 32 | :root{ 33 | --content-max-width: 98%; 34 | } 35 | } 36 | 37 | @media only screen and (max-width:1439px) { 38 | :root{ 39 | --content-max-width: 98%; 40 | } 41 | } 42 | 43 | @media only screen and (max-width:1439px) { 44 | :root{ 45 | --content-max-width: 98%; 46 | } 47 | } 48 | 49 | @media only screen and (max-width:1439px) { 50 | :root{ 51 | --content-max-width: 98%; 52 | } 53 | } 54 | 55 | @media only screen and (max-width:1439px) { 56 | :root{ 57 | --content-max-width: 98%; 58 | } 59 | } 60 | 61 | @media only screen and (max-width:1439px) { 62 | :root{ 63 | --content-max-width: 98%; 64 | } 65 | } 66 | 67 | @media only screen and (max-width:1439px) { 68 | :root{ 69 | --content-max-width: 98%; 70 | } 71 | } 72 | 73 | @media only screen and (max-width:1439px) { 74 | :root{ 75 | --content-max-width: 98%; 76 | } 77 | } 78 | 79 | @media only screen and (max-width:1439px) { 80 | :root{ 81 | --content-max-width: 98%; 82 | } 83 | } 84 | 85 | @media only screen and (max-width:1439px) { 86 | :root{ 87 | --content-max-width: 98%; 88 | } 89 | } 90 | 91 | @media only screen and (max-width:1439px) { 92 | :root{ 93 | --content-max-width: 98%; 94 | } 95 | } 96 | 97 | @media only screen and (max-width:1439px) { 98 | :root{ 99 | --content-max-width: 98%; 100 | } 101 | } 102 | 103 | @media only screen and (max-width:1439px) { 104 | :root{ 105 | --content-max-width: 98%; 106 | } 107 | } 108 | 109 | @media only screen and (max-width:1439px) { 110 | :root{ 111 | --content-max-width: 98%; 112 | } 113 | } 114 | 115 | @media only screen and (max-width:1439px) { 116 | :root{ 117 | --content-max-width: 98%; 118 | } 119 | } 120 | 121 | /* Themes */ 122 | 123 | .ayu { 124 | --bg: hsl(210, 25%, 8%); 125 | --fg: #c5c5c5; 126 | 127 | --sidebar-bg: #14191f; 128 | --sidebar-fg: #c8c9db; 129 | --sidebar-non-existant: #5c6773; 130 | --sidebar-active: #ffb454; 131 | --sidebar-spacer: #2d334f; 132 | 133 | --scrollbar: var(--sidebar-fg); 134 | 135 | --icons: #737480; 136 | --icons-hover: #b7b9cc; 137 | 138 | --links: #0096cf; 139 | 140 | --inline-code-color: #ffb454; 141 | 142 | --theme-popup-bg: #14191f; 143 | --theme-popup-border: #5c6773; 144 | --theme-hover: #191f26; 145 | 146 | --quote-bg: hsl(226, 15%, 17%); 147 | --quote-border: hsl(226, 15%, 22%); 148 | 149 | --table-border-color: hsl(210, 25%, 13%); 150 | --table-header-bg: hsl(210, 25%, 28%); 151 | --table-alternate-bg: hsl(210, 25%, 11%); 152 | 153 | --searchbar-border-color: #848484; 154 | --searchbar-bg: #424242; 155 | --searchbar-fg: #fff; 156 | --searchbar-shadow-color: #d4c89f; 157 | --searchresults-header-fg: #666; 158 | --searchresults-border-color: #888; 159 | --searchresults-li-bg: #252932; 160 | --search-mark-bg: #e3b171; 161 | } 162 | 163 | .coal { 164 | --bg: hsl(200, 7%, 8%); 165 | --fg: #98a3ad; 166 | 167 | --sidebar-bg: #292c2f; 168 | --sidebar-fg: #a1adb8; 169 | --sidebar-non-existant: #505254; 170 | --sidebar-active: #3473ad; 171 | --sidebar-spacer: #393939; 172 | 173 | --scrollbar: var(--sidebar-fg); 174 | 175 | --icons: #43484d; 176 | --icons-hover: #b3c0cc; 177 | 178 | --links: #2b79a2; 179 | 180 | --inline-code-color: #c5c8c6;; 181 | 182 | --theme-popup-bg: #141617; 183 | --theme-popup-border: #43484d; 184 | --theme-hover: #1f2124; 185 | 186 | --quote-bg: hsl(234, 21%, 18%); 187 | --quote-border: hsl(234, 21%, 23%); 188 | 189 | --table-border-color: hsl(200, 7%, 13%); 190 | --table-header-bg: hsl(200, 7%, 28%); 191 | --table-alternate-bg: hsl(200, 7%, 11%); 192 | 193 | --searchbar-border-color: #aaa; 194 | --searchbar-bg: #b7b7b7; 195 | --searchbar-fg: #000; 196 | --searchbar-shadow-color: #aaa; 197 | --searchresults-header-fg: #666; 198 | --searchresults-border-color: #98a3ad; 199 | --searchresults-li-bg: #2b2b2f; 200 | --search-mark-bg: #355c7d; 201 | } 202 | 203 | .light { 204 | --bg: hsl(0, 0%, 100%); 205 | --fg: hsl(0, 0%, 0%); 206 | 207 | --sidebar-bg: #fafafa; 208 | --sidebar-fg: hsl(0, 0%, 0%); 209 | --sidebar-non-existant: #aaaaaa; 210 | --sidebar-active: #1f1fff; 211 | --sidebar-spacer: #f4f4f4; 212 | 213 | --scrollbar: #8F8F8F; 214 | 215 | --icons: #747474; 216 | --icons-hover: #000000; 217 | 218 | --links: #1f1fff; 219 | 220 | --inline-code-color: #F42C4C; 221 | 222 | --theme-popup-bg: #fafafa; 223 | --theme-popup-border: #cccccc; 224 | --theme-hover: #e6e6e6; 225 | 226 | --quote-bg: hsl(197, 37%, 96%); 227 | --quote-border: hsl(197, 37%, 91%); 228 | 229 | --table-border-color: hsl(0, 0%, 95%); 230 | --table-header-bg: hsl(0, 0%, 80%); 231 | --table-alternate-bg: hsl(0, 0%, 97%); 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: #e4f2fe; 240 | --search-mark-bg: #a2cff5; 241 | } 242 | 243 | .navy { 244 | --bg: hsl(226, 23%, 11%); 245 | --fg: #bcbdd0; 246 | 247 | --sidebar-bg: #282d3f; 248 | --sidebar-fg: #c8c9db; 249 | --sidebar-non-existant: #505274; 250 | --sidebar-active: #2b79a2; 251 | --sidebar-spacer: #2d334f; 252 | 253 | --scrollbar: var(--sidebar-fg); 254 | 255 | --icons: #737480; 256 | --icons-hover: #b7b9cc; 257 | 258 | --links: #2b79a2; 259 | 260 | --inline-code-color: #c5c8c6;; 261 | 262 | --theme-popup-bg: #161923; 263 | --theme-popup-border: #737480; 264 | --theme-hover: #282e40; 265 | 266 | --quote-bg: hsl(226, 15%, 17%); 267 | --quote-border: hsl(226, 15%, 22%); 268 | 269 | --table-border-color: hsl(226, 23%, 16%); 270 | --table-header-bg: hsl(226, 23%, 31%); 271 | --table-alternate-bg: hsl(226, 23%, 14%); 272 | 273 | --searchbar-border-color: #aaa; 274 | --searchbar-bg: #aeaec6; 275 | --searchbar-fg: #000; 276 | --searchbar-shadow-color: #aaa; 277 | --searchresults-header-fg: #5f5f71; 278 | --searchresults-border-color: #5c5c68; 279 | --searchresults-li-bg: #242430; 280 | --search-mark-bg: #a2cff5; 281 | } 282 | 283 | .rust { 284 | --bg: hsl(60, 9%, 87%); 285 | --fg: #262625; 286 | 287 | --sidebar-bg: #3b2e2a; 288 | --sidebar-fg: #c8c9db; 289 | --sidebar-non-existant: #505254; 290 | --sidebar-active: #e69f67; 291 | --sidebar-spacer: #45373a; 292 | 293 | --scrollbar: var(--sidebar-fg); 294 | 295 | --icons: #737480; 296 | --icons-hover: #262625; 297 | 298 | --links: #2b79a2; 299 | 300 | --inline-code-color: #6e6b5e; 301 | 302 | --theme-popup-bg: #e1e1db; 303 | --theme-popup-border: #b38f6b; 304 | --theme-hover: #99908a; 305 | 306 | --quote-bg: hsl(60, 5%, 75%); 307 | --quote-border: hsl(60, 5%, 70%); 308 | 309 | --table-border-color: hsl(60, 9%, 82%); 310 | --table-header-bg: #b3a497; 311 | --table-alternate-bg: hsl(60, 9%, 84%); 312 | 313 | --searchbar-border-color: #aaa; 314 | --searchbar-bg: #fafafa; 315 | --searchbar-fg: #000; 316 | --searchbar-shadow-color: #aaa; 317 | --searchresults-header-fg: #666; 318 | --searchresults-border-color: #888; 319 | --searchresults-li-bg: #dec2a2; 320 | --search-mark-bg: #e69f67; 321 | } 322 | 323 | @media (prefers-color-scheme: dark) { 324 | .light.no-js { 325 | --bg: hsl(200, 7%, 8%); 326 | --fg: #98a3ad; 327 | 328 | --sidebar-bg: #292c2f; 329 | --sidebar-fg: #a1adb8; 330 | --sidebar-non-existant: #505254; 331 | --sidebar-active: #3473ad; 332 | --sidebar-spacer: #393939; 333 | 334 | --scrollbar: var(--sidebar-fg); 335 | 336 | --icons: #43484d; 337 | --icons-hover: #b3c0cc; 338 | 339 | --links: #2b79a2; 340 | 341 | --inline-code-color: #c5c8c6;; 342 | 343 | --theme-popup-bg: #141617; 344 | --theme-popup-border: #43484d; 345 | --theme-hover: #1f2124; 346 | 347 | --quote-bg: hsl(234, 21%, 18%); 348 | --quote-border: hsl(234, 21%, 23%); 349 | 350 | --table-border-color: hsl(200, 7%, 13%); 351 | --table-header-bg: hsl(200, 7%, 28%); 352 | --table-alternate-bg: hsl(200, 7%, 11%); 353 | 354 | --searchbar-border-color: #aaa; 355 | --searchbar-bg: #b7b7b7; 356 | --searchbar-fg: #000; 357 | --searchbar-shadow-color: #aaa; 358 | --searchresults-header-fg: #666; 359 | --searchresults-border-color: #98a3ad; 360 | --searchresults-li-bg: #2b2b2f; 361 | --search-mark-bg: #355c7d; 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /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 | 23 | {{#if favicon_svg}} 24 | 25 | {{/if}} 26 | {{#if favicon_png}} 27 | 28 | {{/if}} 29 | 30 | 31 | 32 | {{#if print_enable}} 33 | 34 | {{/if}} 35 | 36 | 37 | 38 | {{#if copy_fonts}} 39 | 40 | {{/if}} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {{#each additional_css}} 49 | 50 | {{/each}} 51 | 52 | {{#if mathjax_support}} 53 | 54 | 55 | {{/if}} 56 | 57 | 58 | 59 | 63 | 64 | 65 | 79 | 80 | 81 | 91 | 92 | 93 | 103 | 104 | 110 | 111 |
112 | 113 |
114 | {{> header}} 115 | 116 | 159 | 160 | {{#if search_enabled}} 161 | 171 | {{/if}} 172 | 173 | 174 | 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 |
234 | 235 | 236 |
237 | 238 | {{{ content }}} 239 |
240 | 241 | 257 |
258 |
259 | 260 | 273 | 274 |
275 | 276 | {{#if livereload}} 277 | 278 | 291 | {{/if}} 292 | 293 | {{#if google_analytics}} 294 | 295 | 310 | {{/if}} 311 | 312 | {{#if playground_line_numbers}} 313 | 316 | {{/if}} 317 | 318 | {{#if playground_copyable}} 319 | 322 | {{/if}} 323 | 324 | {{#if playground_js}} 325 | 326 | 327 | 328 | 329 | 330 | {{/if}} 331 | 332 | {{#if search_js}} 333 | 334 | 335 | 336 | {{/if}} 337 | 338 | 339 | 340 | 341 | 342 | 343 | {{#each additional_js}} 344 | 345 | {{/each}} 346 | 347 | {{#if is_print}} 348 | {{#if mathjax_support}} 349 | 356 | {{else}} 357 | 362 | {{/if}} 363 | {{/if}} 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------