├── .github └── workflows │ └── main.yml ├── .gitignore ├── Changelog.md ├── LICENSE ├── MakeFile ├── README.md ├── book.toml ├── mermaid-init.js ├── mermaid.min.js └── src ├── SUMMARY.md ├── contribution.md ├── img └── org.png ├── overview.md └── safe-guides ├── Appendix ├── best-practice │ ├── intro.md │ ├── qa.md │ └── tips.md ├── cheat-sheet │ ├── Numbers │ │ └── float.md │ └── README.md ├── contribution.md ├── dev_env.md ├── old_guidelines.md ├── optimizing │ └── intro.md ├── rustc-flag.md ├── templates │ ├── clippy.toml.md │ ├── deny.toml.md │ ├── intro.md │ └── rustfmt.toml.md ├── terms.md ├── test.md ├── test │ ├── benchmark.md │ ├── fuzz.md │ └── unit_test.md ├── toc.md └── tools │ ├── cargo-udeps.md │ ├── intro.md │ ├── noisy-clippy.md │ └── rustfmt.md ├── code_style.md ├── code_style ├── comments.md ├── comments │ ├── G.CMT.01.md │ ├── G.CMT.02.md │ ├── G.CMT.03.md │ ├── P.CMT.01.md │ ├── P.CMT.02.md │ ├── P.CMT.03.md │ ├── P.CMT.04.md │ └── P.CMT.05.md ├── fmt.md ├── fmt │ ├── P.FMT.01.md │ ├── P.FMT.02.md │ ├── P.FMT.03.md │ ├── P.FMT.04.md │ ├── P.FMT.05.md │ ├── P.FMT.06.md │ ├── P.FMT.07.md │ ├── P.FMT.08.md │ ├── P.FMT.09.md │ ├── P.FMT.10.md │ ├── P.FMT.11.md │ ├── P.FMT.12.md │ ├── P.FMT.13.md │ ├── P.FMT.14.md │ ├── P.FMT.15.md │ └── P.FMT.16.md ├── naming.md └── naming │ ├── G.NAM.01.md │ ├── G.NAM.02.md │ ├── P.NAM.01.md │ ├── P.NAM.02.md │ ├── P.NAM.03.md │ ├── P.NAM.04.md │ ├── P.NAM.05.md │ ├── P.NAM.06.md │ ├── P.NAM.07.md │ ├── P.NAM.08.md │ └── P.NAM.09.md ├── coding_practice.md ├── coding_practice ├── async-await.md ├── async-await │ ├── G.ASY.01.md │ ├── G.ASY.02.md │ ├── G.ASY.03.md │ ├── G.ASY.04.md │ ├── G.ASY.05.md │ └── P.ASY.01.md ├── cargo.md ├── cargo │ ├── G.CAR.01.md │ ├── G.CAR.02.md │ ├── G.CAR.03.md │ ├── G.CAR.04.md │ ├── P.CAR.01.md │ ├── P.CAR.02.md │ ├── P.CAR.03.md │ └── P.CAR.04.md ├── code-generation.md ├── code-generation │ ├── P.CGN.01.md │ └── P.CGN.02.md ├── collections.md ├── collections │ ├── G.CLT.01.md │ └── P.CLT.01.md ├── consts.md ├── consts │ ├── G.CNS.01.md │ ├── G.CNS.02.md │ ├── G.CNS.03.md │ ├── G.CNS.04.md │ └── G.CNS.05.md ├── control-flow.md ├── control-flow │ ├── G.CTF.01.md │ ├── G.CTF.02.md │ ├── G.CTF.03.md │ ├── G.CTF.04.md │ ├── P.CTF.01.md │ └── P.CTF.02.md ├── data-type.md ├── data-type │ ├── G.TYP.01.md │ ├── G.TYP.02.md │ ├── G.TYP.03.md │ ├── P.TYP.01.md │ ├── array.md │ ├── array │ │ ├── G.TYP.ARR.01.md │ │ ├── G.TYP.ARR.02.md │ │ └── G.TYP.ARR.03.md │ ├── bool.md │ ├── bool │ │ ├── G.TYP.BOL.01.md │ │ ├── G.TYP.BOL.02.md │ │ ├── G.TYP.BOL.03.md │ │ ├── G.TYP.BOL.04.md │ │ ├── G.TYP.BOL.05.md │ │ ├── G.TYP.BOL.06.md │ │ └── G.TYP.BOL.07.md │ ├── char.md │ ├── char │ │ ├── G.TYP.CHR.01.md │ │ ├── G.TYP.CHR.02.md │ │ └── G.TYP.CHR.03.md │ ├── enum.md │ ├── enum │ │ ├── G.TYP.ENM.01.md │ │ ├── G.TYP.ENM.02.md │ │ ├── G.TYP.ENM.03.md │ │ ├── G.TYP.ENM.04.md │ │ ├── G.TYP.ENM.05.md │ │ ├── G.TYP.ENM.06.md │ │ └── G.TYP.ENM.07.md │ ├── float.md │ ├── float │ │ ├── G.TYP.FLT.01.md │ │ ├── G.TYP.FLT.02.md │ │ ├── G.TYP.FLT.03.md │ │ ├── G.TYP.FLT.04.md │ │ └── G.TYP.FLT.05.md │ ├── int.md │ ├── int │ │ ├── G.TYP.INT.01.md │ │ ├── G.TYP.INT.02.md │ │ └── G.TYP.INT.03.md │ ├── ref.md │ ├── ref │ │ └── .keep │ ├── slice.md │ ├── slice │ │ ├── P.TYP.SLC.01.md │ │ └── P.TYP.SLC.02.md │ ├── struct.md │ ├── struct │ │ ├── G.TYP.SCT.01.md │ │ ├── G.TYP.SCT.02.md │ │ ├── G.TYP.SCT.03.md │ │ ├── P.TYP.SCT.01.md │ │ └── P.TYP.SCT.02.md │ ├── tuple.md │ ├── tuple │ │ └── G.TYP.TUP.01.md │ ├── unit.md │ ├── unit │ │ └── .keep │ ├── vec.md │ └── vec │ │ ├── G.TYP.VEC.01.md │ │ ├── P.TYP.VEC.01.md │ │ └── P.TYP.VEC.02.md ├── error-handle.md ├── error-handle │ ├── G.ERR.01.md │ ├── G.ERR.02.md │ ├── P.ERR.01.md │ └── P.ERR.02.md ├── expr.md ├── expr │ ├── G.EXP.01.md │ ├── G.EXP.02.md │ ├── G.EXP.03.md │ ├── G.EXP.04.md │ ├── G.EXP.05.md │ └── G.EXP.06.md ├── fn-design.md ├── fn-design │ ├── G.FUD.01.md │ ├── G.FUD.02.md │ ├── G.FUD.03.md │ ├── G.FUD.04.md │ ├── G.FUD.05.md │ ├── G.FUD.06.md │ ├── P.FUD.01.md │ └── P.FUD.02.md ├── generic.md ├── generic │ ├── G.GEN.01.md │ ├── G.GEN.02.md │ ├── P.GEN.01.md │ ├── P.GEN.02.md │ ├── P.GEN.03.md │ ├── P.GEN.04.md │ └── P.GEN.05.md ├── io.md ├── io │ ├── G.FIO.01.md │ └── P.FIO.01.md ├── macros.md ├── macros │ ├── G.MAC.01.md │ ├── G.MAC.02.md │ ├── P.MAC.01.md │ ├── P.MAC.02.md │ ├── decl.md │ ├── decl │ │ ├── P.MAC.DCL.01.md │ │ ├── P.MAC.DCL.02.md │ │ ├── P.MAC.DCL.03.md │ │ ├── P.MAC.DCL.04.md │ │ ├── P.MAC.DCL.05.md │ │ ├── P.MAC.DCL.06.md │ │ ├── P.MAC.DCL.07.md │ │ └── P.MAC.DCL.08.md │ ├── proc.md │ └── proc │ │ ├── P.MAC.PRO.01.md │ │ ├── P.MAC.PRO.02.md │ │ ├── P.MAC.PRO.03.md │ │ └── P.MAC.PRO.04.md ├── memory.md ├── memory │ ├── box.md │ ├── box │ │ ├── G.MEM.BOX.01.md │ │ ├── G.MEM.BOX.02.md │ │ └── G.MEM.BOX.03.md │ ├── drop.md │ ├── drop │ │ └── G.MEM.DRP.01.md │ ├── lifetime.md │ ├── lifetime │ │ ├── P.MEM.LFT.01.md │ │ └── P.MEM.LFT.02.md │ ├── smart-ptr.md │ └── smart-ptr │ │ └── P.MEM.SPT.01.md ├── module.md ├── module │ ├── G.MOD.01.md │ ├── G.MOD.02.md │ ├── G.MOD.03.md │ ├── G.MOD.04.md │ ├── G.MOD.05.md │ ├── P.MOD.01.md │ └── P.MOD.02.md ├── no-std.md ├── no-std │ ├── P.EMB.01.md │ └── P.EMB.02.md ├── others.md ├── others │ ├── G.OTH.01.md │ └── G.OTH.02.md ├── security.md ├── security │ ├── G.SEC.01.md │ └── P.SEC.01.md ├── statics.md ├── statics │ └── G.STV.01.md ├── strings.md ├── strings │ ├── G.STR.01.md │ ├── G.STR.02.md │ ├── G.STR.03.md │ ├── G.STR.04.md │ ├── G.STR.05.md │ ├── P.STR.01.md │ ├── P.STR.02.md │ ├── P.STR.03.md │ ├── P.STR.04.md │ └── P.STR.05.md ├── threads.md ├── threads │ ├── lock-free.md │ ├── lock-free │ │ ├── P.MTH.LKF.01.md │ │ └── P.MTH.LKF.02.md │ ├── lock.md │ └── lock │ │ ├── G.MTH.LCK.01.md │ │ ├── G.MTH.LCK.02.md │ │ ├── G.MTH.LCK.03.md │ │ ├── G.MTH.LCK.04.md │ │ └── P.MTH.LCK.01.md ├── traits.md ├── traits │ ├── P.TRA.01.md │ ├── std-builtin.md │ ├── std-builtin │ │ ├── G.TRA.BLN.01.md │ │ ├── G.TRA.BLN.02.md │ │ ├── G.TRA.BLN.03.md │ │ ├── G.TRA.BLN.04.md │ │ ├── G.TRA.BLN.05.md │ │ ├── G.TRA.BLN.06.md │ │ ├── G.TRA.BLN.07.md │ │ ├── G.TRA.BLN.08.md │ │ ├── G.TRA.BLN.09.md │ │ ├── G.TRA.BLN.10.md │ │ └── P.TRA.BLN.01.md │ ├── trait-object.md │ └── trait-object │ │ ├── P.TRA.OBJ.01.md │ │ └── P.TRA.OBJ.02.md ├── unsafe_rust.md ├── unsafe_rust │ ├── G.UNS.01.md │ ├── P.UNS.01.md │ ├── P.UNS.02.md │ ├── P.UNS.03.md │ ├── ffi.md │ ├── ffi │ │ ├── P.UNS.FFI.01.md │ │ ├── P.UNS.FFI.02.md │ │ ├── P.UNS.FFI.03.md │ │ ├── P.UNS.FFI.04.md │ │ ├── P.UNS.FFI.05.md │ │ ├── P.UNS.FFI.06.md │ │ ├── P.UNS.FFI.07.md │ │ ├── P.UNS.FFI.08.md │ │ ├── P.UNS.FFI.09.md │ │ ├── P.UNS.FFI.10.md │ │ ├── P.UNS.FFI.11.md │ │ ├── P.UNS.FFI.12.md │ │ ├── P.UNS.FFI.13.md │ │ ├── P.UNS.FFI.14.md │ │ ├── P.UNS.FFI.15.md │ │ ├── P.UNS.FFI.16.md │ │ ├── P.UNS.FFI.17.md │ │ └── P.UNS.FFI.18.md │ ├── glossary.md │ ├── io.md │ ├── io │ │ └── P.UNS.FIO.01.md │ ├── mem.md │ ├── mem │ │ ├── G.UNS.MEM.01.md │ │ ├── P.UNS.MEM.01.md │ │ ├── P.UNS.MEM.02.md │ │ ├── P.UNS.MEM.03.md │ │ ├── P.UNS.MEM.04.md │ │ └── P.UNS.MEM.05.md │ ├── raw_ptr.md │ ├── raw_ptr │ │ ├── G.UNS.PTR.01.md │ │ ├── G.UNS.PTR.02.md │ │ ├── G.UNS.PTR.03.md │ │ ├── P.UNS.PTR.01.md │ │ ├── P.UNS.PTR.02.md │ │ └── P.UNS.PTR.03.md │ ├── safe_abstract.md │ ├── safe_abstract │ │ ├── G.UNS.SAS.01.md │ │ ├── G.UNS.SAS.02.md │ │ ├── P.UNS.SAS.01.md │ │ ├── P.UNS.SAS.02.md │ │ ├── P.UNS.SAS.03.md │ │ ├── P.UNS.SAS.04.md │ │ ├── P.UNS.SAS.05.md │ │ ├── P.UNS.SAS.06.md │ │ ├── P.UNS.SAS.07.md │ │ ├── P.UNS.SAS.08.md │ │ └── P.UNS.SAS.09.md │ ├── union.md │ └── union │ │ ├── P.UNS.UNI.01.md │ │ └── P.UNS.UNI.02.md ├── variables.md └── variables │ ├── G.VAR.01.md │ ├── G.VAR.02.md │ ├── G.VAR.03.md │ ├── G.VAR.04.md │ ├── P.VAR.01.md │ └── P.VAR.02.md └── overview ├── convention.md └── why.md /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | # Add explicit permissions - this is crucial! 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | name: Build, Test and Deploy 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 # Important for git history 20 | 21 | - uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | 25 | - name: Install mdbook 26 | run: | 27 | mkdir bin 28 | curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin 29 | echo "$(pwd)/bin" >> $GITHUB_PATH 30 | 31 | - run: mdbook build 32 | 33 | - name: Deploy to GitHub Pages 34 | uses: JamesIves/github-pages-deploy-action@v4 35 | with: 36 | folder: book 37 | branch: gh-pages 38 | token: ${{ secrets.PAT }} # Explicitly use the token 39 | clean: true # Clean up old files 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | .DS_Store -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## V 0.2 评审版本发布 2 | 3 | 任何事情如果想做到恰到好处,都需要一个进化的过程。编码规范也不例外。 4 | 5 | 1. 统一了规范的文本格式与措辞。 6 | 2. 经过考量,将一些原则变为了规则,并增加了自定义lint的说明。 7 | 3. 删除了一些不需要放到规范中条目。 8 | 4. 为一些规则丰富和精简了很多代码示例。 9 | 5. 移除规范中引用的不符合`MIT/Apache/Mozilla public licenses` 的开源配置和代码示例。 10 | 11 | ## V 0.3 发布 12 | 13 | 改进: 14 | 15 | - 将当前无法使用 Clippy 检查的规则(G)统一修改为了原则(P) 16 | - 删除和修复一些条款 17 | - 新增 信息安全 `P.SEC.01` 条款 18 | 19 | 20 | ## V 1.0 beta 发布 21 | 22 | 改进: 23 | 24 | - 更新目录结构 25 | - 对文字和代码整体做了一遍评审,改进文字描述和代码格式 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rust Coding Guidelines (Unofficial) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MakeFile: -------------------------------------------------------------------------------- 1 | .PHONY: deploy 2 | 3 | init: 4 | git worktree add -f /tmp/rustguidebook gh-pages 5 | git worktree remove -f /tmp/rustguidebook 6 | git worktree add -f /tmp/rustguidebook gh-pages 7 | 8 | 9 | deploy: init 10 | @echo "====> deploying to github" 11 | mdbook build 12 | rm -rf /tmp/rustguidebook/* 13 | cp -rp book/* /tmp/rustguidebook/ 14 | cd /tmp/rustguidebook && \ 15 | git add -A && \ 16 | git commit -m "deployed on $(shell date) by ${USER}" && \ 17 | git push origin gh-pages -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["blackanger"] 3 | language = "zh" 4 | multilingual = false 5 | src = "src" 6 | title = "Rust 编码规范 V 1.0 beta" 7 | 8 | [build] 9 | create-missing = true 10 | 11 | [preprocessor.toc] 12 | command = "mdbook-toc" 13 | renderer = ["html"] 14 | max-level = 2 15 | 16 | [preprocessor.mermaid] 17 | command = "mdbook-mermaid" 18 | 19 | [output.html] 20 | git-repository-url = "https://github.com/Rust-Coding-Guidelines/rust-coding-guidelines-zh" 21 | additional-js = ["mermaid.min.js", "mermaid-init.js"] 22 | 23 | [output.html.fold] 24 | enable = true 25 | level = 0 26 | -------------------------------------------------------------------------------- /mermaid-init.js: -------------------------------------------------------------------------------- 1 | mermaid.initialize({startOnLoad:true}); 2 | -------------------------------------------------------------------------------- /src/contribution.md: -------------------------------------------------------------------------------- 1 | # 贡献者指南 2 | 3 | 目前项目处于初期,先以 issues 提建议为主,暂不支持 Pull Request。 4 | 5 | 等项目整体结构确定以后,再开始接受 Pull Request。 6 | 7 | 8 | ### 贡献名单 9 | 10 | -------------------------------------------------------------------------------- /src/img/org.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rust-Coding-Guidelines/rust-coding-guidelines-zh/6b3fc48b285b4f87696634a3e18572d010b30fd4/src/img/org.png -------------------------------------------------------------------------------- /src/overview.md: -------------------------------------------------------------------------------- 1 | # 1. 概述 2 | 3 | ## 状态 4 | 5 | - 《Rust 编码规范》目前为 V 1.0 beta 试行版,改进内容参考 [Changelog](./Changelog.md) 6 | 7 | ## 详细 8 | 9 | - [1.1 为什么需要 Rust 编码规范](./overview/why.md) 10 | - [1.2 编码规范基本约定](./overview/convention.md) 11 | 12 | ## 介绍 13 | 14 | Rust 语言社区内其实分散着很多编码规范,下面罗列一部分公开信息: 15 | 16 | - [官方|Rust API 编写指南](https://rust-lang.github.io/api-guidelines/about.html) 17 | - [官方 | Rust Style Guide](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md) 18 | - [Rust's Unsafe Code Guidelines Reference](https://rust-lang.github.io/unsafe-code-guidelines/) 19 | - [法国国家信息安全局 | Rust 安全(Security)规范](https://anssi-fr.github.io/rust-guide) 20 | - [Apache Teaclave 安全计算平台 | Rust 开发规范](https://teaclave.apache.org/docs/rust-guildeline/) 21 | - [PingCAP | 编码风格指南(包括 Rust 和 Go 等)](https://github.com/pingcap/style-guide) 22 | - [Google Fuchsia 操作系统 Rust 开发指南](https://fuchsia.dev/fuchsia-src/development/languages/rust) 23 | - [RustAnalyzer 编码风格指南](https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/dev/style.md) 24 | - [使用 Rust 设计优雅的 API](https://deterministic.space/elegant-apis-in-rust.html) 25 | - [Rust FFI 指南](https://michael-f-bryan.github.io/rust-ffi-guide/) 26 | 27 | 上面这些除了 Rust 官方和法国国家信息安全局的编码规范之外,其他开源项目的编码规范主要是为了规范贡献者们遵循一个统一的编码风格。 28 | 29 | 所以,一个通用的,覆盖编码风格和具体编码实践的全面的编码规范,更有助于社区各个开源项目和各大公司参考去制定自己的编码规范。 30 | 31 | ![org](./img/org.png) 32 | 33 | 本规范致力于成为统一的 Rust 编码规范,各大公司可以依赖本规范,结合自己的业务领域和团队习惯,形成自己的编码规范,并可以在日常实践中反哺本规范,让本规范更加完善。 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/best-practice/intro.md: -------------------------------------------------------------------------------- 1 | # I.最佳实践 2 | 3 | ## 列表 4 | 5 | - [Q&A](./qa.md) 6 | - [Tips](./tips.md) -------------------------------------------------------------------------------- /src/safe-guides/Appendix/best-practice/qa.md: -------------------------------------------------------------------------------- 1 | # 初学者常见问题Q&A 2 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/cheat-sheet/README.md: -------------------------------------------------------------------------------- 1 | # F.Cheat Sheet 2 | 3 | 这里用于梳理 Rust 相关的 Cheat Sheet。 4 | 5 | - [数字]() 6 | - [浮点数](./safe-guides/Appendix/cheat-sheet/Numbers/float.md) 7 | 8 | 9 | 10 | ## 资源 11 | 12 | [https://cheats.rs/](https://cheats.rs/) 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/contribution.md: -------------------------------------------------------------------------------- 1 | # J.贡献说明 2 | 3 | 欢迎直接提交 Issues 或 PR 直接参与评审和完善(包括精简、增补新的规则)编码规范。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/templates/deny.toml.md: -------------------------------------------------------------------------------- 1 | # Cargo Deny 配置模板 2 | 3 | [cargo-deny](https://github.com/EmbarkStudios/cargo-deny) 是检查 Cargo 依赖的一个 Lint 工具。它检查的范围包括: 4 | 5 | - Licenses,检查依赖crate许可证是否合规。 6 | - Bans, 检查被禁止使用的依赖 crate。 7 | - Advisories ,检查有安全缺陷漏洞或停止维护的 依赖 crate。 8 | - Source,检查依赖crate 的来源,确保只来自于可信任的来源。 9 | 10 | 以下是模板(参考 [vectordotdev/vector 的 deny.toml](https://github.com/vectordotdev/vector/blob/master/deny.toml)): 11 | 12 | ```toml 13 | [licenses] 14 | allow = [ 15 | "MIT", 16 | "CC0-1.0", 17 | "ISC", 18 | "OpenSSL", 19 | "Unlicense", 20 | "BSD-2-Clause", 21 | "BSD-3-Clause", 22 | "Apache-2.0", 23 | "Apache-2.0 WITH LLVM-exception", 24 | "Zlib", 25 | ] 26 | 27 | unlicensed = "warn" 28 | default = "warn" 29 | 30 | private = { ignore = true } 31 | 32 | [[licenses.clarify]] 33 | name = "ring" 34 | version = "*" 35 | expression = "MIT AND ISC AND OpenSSL" 36 | license-files = [ 37 | { path = "LICENSE", hash = 0xbd0eed23 } 38 | ] 39 | 40 | [advisories] 41 | ignore = [ 42 | # term is looking for a new maintainer 43 | # https://github.com/timberio/vector/issues/6225 44 | "RUSTSEC-2018-0015", 45 | 46 | # `net2` crate has been deprecated; use `socket2` instead 47 | # https://github.com/timberio/vector/issues/5582 48 | "RUSTSEC-2020-0016", 49 | 50 | # Type confusion if __private_get_type_id__ is overriden 51 | # https://github.com/timberio/vector/issues/5583 52 | "RUSTSEC-2020-0036", 53 | 54 | # stdweb is unmaintained 55 | # https://github.com/timberio/vector/issues/5585 56 | "RUSTSEC-2020-0056", 57 | ] 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/templates/intro.md: -------------------------------------------------------------------------------- 1 | # D.模版 2 | 3 | 这里记录一些 rustfmt 和 clippy 等相关工具等配置文件模版。 4 | 5 | - [rustfmt](./rustfmt.toml.md) 6 | - [clippy](./clippy.toml.md) 7 | - [deny](./deny.toml.md) 8 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/test.md: -------------------------------------------------------------------------------- 1 | # B.测试 2 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/test/fuzz.md: -------------------------------------------------------------------------------- 1 | # 模糊测试 2 | 3 | [模糊测试(Fuzz testing)](https://en.wikipedia.org/wiki/Fuzz_testing)是一种软件测试技术,用于通过向软件提供伪随机数据作为输入来发现安全性和稳定性问题。 4 | 5 | 关于模糊测试可以参考: 6 | 7 | - [Rust Fuzz Book](https://rust-fuzz.github.io/book/introduction.html) 8 | - [https://github.com/rust-fuzz](https://github.com/rust-fuzz) 9 | - [How to fuzz Rust code continuously](https://about.gitlab.com/blog/2020/12/03/how-to-fuzz-rust-code/) 10 | 11 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/test/unit_test.md: -------------------------------------------------------------------------------- 1 | # 单元测试 2 | 3 | Rust 支持单元测试。 4 | 5 | ## 测试代码组织 6 | 7 | 对于内部函数,单元测试代码最好放到业务代码的同一个模块下。 8 | 9 | 对于外部接口,单元测试最好放到独立的 `tests` 目录。 10 | 11 | ## 文档测试 12 | 13 | 对所有对外接口进行文档测试是一个不错的开始。 14 | 15 | ## 编译测试 16 | 17 | 通过 `compiletest` 来测试某些代码可能无法编译。 参考: [Rustc开发指南](https://rustc-dev-guide.rust-lang.org/tests/adding.html#ui) 18 | 19 | ## 随机测试 20 | 21 | 使用 第三方库`proptest` 来进行随机测试。 22 | 23 | ```rust 24 | use proptest::prelude::*; 25 | 26 | proptest! { 27 | #[test] 28 | fn check_count_correct(haystack: Vec, needle: u8) { 29 | prop_assert_eq!(count(&haystack, needle), naive_count(&haystack, needle)); 30 | } 31 | } 32 | ``` 33 | 34 | ## 代码测试率覆盖检测工具 35 | 36 | [tarpaulin](https://github.com/xd009642/tarpaulin) 是 Cargo 构建系统的代码覆盖率报告工具,目前 **仅支持运行 Linux 的 x86_64 处理器**。 37 | 38 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/toc.md: -------------------------------------------------------------------------------- 1 | # 附录 2 | 3 | - [A.开发环境](./dev_env.md) 4 | - [B.测试](./test.md) 5 | - [单元测试](./test/unit_test.md) 6 | - [基准测试](./test/benchmark.md) 7 | - [模糊测试](./test/fuzz.md) 8 | - [C.术语解释](./terms.md) 9 | - [D.模板](./templates/intro.md) 10 | - [rustfmt 模板](./templates/rustfmt.toml.md) 11 | - [clippy 模板](./templates/clippy.toml.md) 12 | - [deny 模板](./templates/deny.toml.md) 13 | - [E.工具链](./tools/intro.md) 14 | - [rustfmt](./tools/rustfmt.md) 15 | - [noisy-clippy](./tools/noisy-clippy.md) 16 | - [cargo-udeps](./tools/cargo-udeps.md) 17 | - [F.Cheat Sheet](./cheat-sheet/README.md) 18 | - [浮点数](./cheat-sheet/Numbers/float.md) 19 | - [G.优化指南](./optimizing/intro.md) 20 | - [H.编译参数说明](./rustc-flag.md) 21 | - [I.最佳实践](./best-practice/intro.md) 22 | - [初学者常见问题Q&A](./best-practice/qa.md) 23 | - [Rust 编程技巧](./best-practice/tips.md) 24 | - [J.贡献说明](./contribution.md) 25 | - [K.淘汰的规则](./old_guidelines.md) 26 | 27 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/tools/cargo-udeps.md: -------------------------------------------------------------------------------- 1 | # Cargo Udeps 2 | 3 | [cargo-udeps](https://github.com/est31/cargo-udeps) 检查 `Cargo.toml` 中未使用的依赖。 4 | 5 | `cargo udeps` 对标的是` rustc` 的` unused_crate_dependencies lint` 6 | 7 | 虽然 rustc 也能检查一些未使用依赖,但是在 lib 和 bin 混合的项目中误报率高 8 | 9 | ``` 10 | RUSTFLAGS="-Dunused_crate_dependencies" cargo c 11 | ``` 12 | 13 | `cargo udeps` 的最大优点就是**几乎没有误报**。 14 | 15 | 但是检查力度不如` rustc unused_crate_dependencies lint `仔细,建议二者搭配使用 16 | 17 | -------------------------------------------------------------------------------- /src/safe-guides/Appendix/tools/intro.md: -------------------------------------------------------------------------------- 1 | # E.工具链 2 | 3 | 这里介绍一些检测工具,比如 Cargo fmt 和 Cargo Clippy. 4 | 5 | 6 | 7 | ## 参考资料 8 | 9 | 1. [https://doc.rust-lang.org/rustc/lints/groups.html](https://doc.rust-lang.org/rustc/lints/groups.html) 10 | 2. [https://rust-lang.github.io/rust-clippy/master/index.html](https://rust-lang.github.io/rust-clippy/master/index.html) 11 | 3. [https://rust-lang.github.io/rust-clippy/master/index.html](https://rust-lang.github.io/rust-clippy/master/index.html) 12 | 4. [Dtolnay 对 crates.io 中 clippy lint 应用统计](https://github.com/dtolnay/noisy-clippy) -------------------------------------------------------------------------------- /src/safe-guides/Appendix/tools/noisy-clippy.md: -------------------------------------------------------------------------------- 1 | # 在 Rust 生态中被拒绝的一些默认开启的lint 2 | 3 | 来源:[https://github.com/dtolnay/noisy-clippy](https://github.com/dtolnay/noisy-clippy) 4 | 5 | 以下按字母顺序排列。 6 | 7 | ## `absurd_extreme_comparisons` 8 | 9 | [https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons](https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons) 10 | 11 | 【描述】 12 | 13 | 默认为 `Deny`,但在实际应用中,多被设置为 `allow`。 14 | 15 | 16 | ## `blacklisted_name` 17 | 18 | [https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name](https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name) 19 | 20 | 【描述】 21 | 22 | 该 lint 不允许代码中出现 「内置黑名单」中定义的命名,比如 `foo`、`baz`。 23 | 24 | 默认为 `Warn`,但在实际应用中,可能被设置为`allow`,因为在某些样板代码、文档或测试代码中可能需要使用 `foo`。 25 | 26 | 27 | ## `blanket_clippy_restriction_lints` 28 | 29 | [https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints](https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints) 30 | 31 | 【描述】 32 | 33 | 用于检查针对整个 `clippy::restriction` 类别的警告/拒绝/禁止属性。Restriction lint 有时与其他 lint 形成对比,甚至与惯用的 Rust 背道而驰。 这些 lint 应仅在逐个 lint 的基础上启用并仔细考虑。 34 | 35 | 默认为 `suspicious/warn`,但实际有些项目中会将其设置为 `allow`。 -------------------------------------------------------------------------------- /src/safe-guides/code_style.md: -------------------------------------------------------------------------------- 1 | # 2. 代码风格 2 | 3 | 代码风格包含标识符的命名风格、排版与格式风格、注释风格等。一致的编码习惯与风格,可以提高代码可读性和可维护性。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments.md: -------------------------------------------------------------------------------- 1 | # 2.3 注释与文档 2 | 3 | 在 Rust 中,注释分为两类:普通注释和文档注释。普通注释使用 `//` 或 `/* ... */`,文档注释使用 `///`、`//!` 或 `/** ... **/`。 4 | 5 | 在原则和规则中提到「注释」时,包括普通注释和文档注释。当提到「文档」时,特指文档注释。 6 | 7 | 8 | ## 参考 9 | 10 | 1. [RFC 505: API 注释约定](https://github.com/rust-lang/rfcs/blob/master/text/0505-api-comment-conventions.md) 11 | 2. [RFC 1574: API 文档约定](https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md) 12 | 3. [Making Great Docs with Rustdoc](https://www.tangramvision.com/blog/making-great-docs-with-rustdoc) 13 | 4. [Rust Doc book](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html) 14 | 15 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/G.CMT.01.md: -------------------------------------------------------------------------------- 1 | ## G.CMT.01 在公开的返回`Result`类型的函数文档中增加 Error 注释 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在公开(pub)的返回`Result`类型的函数文档中,建议增加 `# Error` 注释来解释什么场景下该函数会返回什么样的错误类型,方便用户处理错误。 8 | 9 | 说明: 该规则可以通过 cargo clippy 来检测,但默认不会警告。 10 | 11 | **【反例】** 12 | 13 | 14 | ```rust 15 | #![warn(clippy::missing_errors_doc)] 16 | 17 | use std::io; 18 | // 不符合: Clippy 会警告 "warning: docs for function returning `Result` missing `# Errors` section" 19 | pub fn read(filename: String) -> io::Result { 20 | unimplemented!(); 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | #![warn(clippy::missing_errors_doc)] 28 | 29 | use std::io; 30 | // 符合:增加了规范的 Errors 文档注释 31 | 32 | /// # Errors 33 | /// 34 | /// Will return `Err` if `filename` does not exist or the user does not have 35 | /// permission to read it. 36 | pub fn read(filename: String) -> io::Result { 37 | unimplemented!(); 38 | } 39 | ``` 40 | 41 | **【Lint 检测】** 42 | 43 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认 level | 44 | | -------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | ---------- | 45 | | [missing_errors_doc](https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc) | yes | no | Style | allow | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/G.CMT.02.md: -------------------------------------------------------------------------------- 1 | ## G.CMT.02 如果公开的API在某些情况下会发生Panic,则相应文档中需增加 Panic 注释 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 在公开(pub)函数文档中,建议增加 `# Panic` 注释来解释该函数在什么条件下会 Panic,便于使用者进行预处理。 8 | 9 | 说明: 该规则通过 cargo clippy 来检测。默认不会警告。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | #![warn(clippy::missing_panics_doc)] 15 | 16 | // 不符合:没有添加 Panic 相关的文档注释,Clippy会报错 "warning: docs for function which may panic missing `# Panics` section"。 17 | pub fn divide_by(x: i32, y: i32) -> i32 { 18 | if y == 0 { 19 | panic!("Cannot divide by 0") 20 | } else { 21 | x / y 22 | } 23 | } 24 | ``` 25 | 26 | **【正例】** 27 | 28 | ```rust 29 | #![warn(clippy::missing_panics_doc)] 30 | 31 | // 符合:增加了规范的 Panic 注释 32 | /// # Panics 33 | /// 34 | /// Will panic if y is 0 35 | pub fn divide_by(x: i32, y: i32) -> i32 { 36 | if y == 0 { 37 | panic!("Cannot divide by 0") 38 | } else { 39 | x / y 40 | } 41 | } 42 | ``` 43 | 44 | **【Lint 检测】** 45 | 46 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认 level | 47 | | -------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | ---------- | 48 | | [missing_panics_doc](https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc) | yes | no | Style | allow | 49 | 50 | 默认为 `allow`,但是此规则需要设置`#![warn(clippy::missing_panics_doc)]`。 -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/G.CMT.03.md: -------------------------------------------------------------------------------- 1 | ## G.CMT.03 在文档注释中要使用空格代替 tab 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 代码风格中提倡使用**四个空格**代替tab,在文档注释中也应该统一使用**四个空格**。 8 | 9 | **【反例】** 10 | 11 | 下面文档注释中使用了 tab。 12 | 13 | ```rust 14 | // 不符合:文档注释中使用了 tab 缩进 15 | /// 16 | /// Struct to hold two strings: 17 | /// - first one 18 | /// - second one 19 | pub struct DoubleString { 20 | /// 21 | /// - First String: 22 | /// - needs to be inside here 23 | first_string: String, 24 | /// 25 | /// - Second String: 26 | /// - needs to be inside here 27 | second_string: String, 28 | } 29 | ``` 30 | 31 | **【正例】** 32 | 33 | ```rust 34 | // 符合:文档注释中使用了四个空格缩进 35 | /// 36 | /// Struct to hold two strings: 37 | /// - first one 38 | /// - second one 39 | pub struct DoubleString { 40 | /// 41 | /// - First String: 42 | /// - needs to be inside here 43 | first_string: String, 44 | /// 45 | /// - Second String: 46 | /// - needs to be inside here 47 | second_string: String, 48 | } 49 | ``` 50 | 51 | **【Lint 检测】** 52 | 53 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认 level | 54 | | ------------------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | ---------- | 55 | | [tabs_in_doc_comments](https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments) | yes | no | Style | warn | 56 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/P.CMT.02.md: -------------------------------------------------------------------------------- 1 | ## P.CMT.02 注释应该有宽度限制 2 | 3 | **【描述】** 4 | 5 | 每行注释的宽度不能过长,需要设置一定的宽度,不超过120,有助于提升可读性。 6 | 7 | `rustfmt`中通过`comment_width`配合 `wrap_comments` 配置项,可将超过宽度限制的注释自动分割为多行。 8 | 9 | 注意:`rustfmt`的 `use_small_heuristics`配置项并不包括`comment_width`。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | // 不符合 15 | // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 16 | ``` 17 | 18 | **【正例】** 19 | 20 | 当 `comment_width=80` 且 `wrap_comments=true`时。 21 | 22 | 注意:这里 `wrap_comments`并未使用默认值,需要配置为 true。 23 | 24 | ```rust 25 | // 符合 26 | // Lorem ipsum dolor sit amet, consectetur adipiscing elit, 27 | // sed do eiusmod tempor incididunt ut labore et dolore 28 | // magna aliqua. Ut enim ad minim veniam, quis nostrud 29 | // exercitation ullamco laboris nisi ut aliquip ex ea 30 | // commodo consequat. 31 | ``` 32 | 33 | **【rustfmt 配置】** 34 | 35 | | 对应选项 | 可选值 | 是否 stable | 说明 | 36 | | ------ | ---- | ---- | ---- | 37 | | [`comment_width`](https://rust-lang.github.io/rustfmt/?#comment_width) | 80(默认) | No| 指定一行注释允许的最大宽度 | 38 | | [`wrap_comments`](https://rust-lang.github.io/rustfmt/?#wrap_comments) | false(默认),true(建议) | No| 运行多行注释按最大宽度自动换成多行注释 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/P.CMT.03.md: -------------------------------------------------------------------------------- 1 | ## P.CMT.03 使用行注释而避免使用块注释 2 | 3 | **【描述】** 4 | 5 | 尽量使用行注释(`//` 或 `///`),而非块注释。这是Rust社区的约定俗成。 6 | 7 | 对于文档注释,仅在编写模块级文档时使用 `//!`,在其他情况使用 `///`更好。 8 | 9 | 说明: `#![doc]` 和 `#[doc]` 对于简化文档注释有特殊作用,没有必要通过 `rustfmt` 将其强制转化为 `//!` 或 `///` 。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | // 不符合 15 | 16 | /* 17 | * Wait for the main task to return, and set the process error code 18 | * appropriately. 19 | */ 20 | mod tests { 21 | //! This module contains tests 22 | 23 | // ... 24 | } 25 | ``` 26 | 27 | **【正例】** 28 | 29 | 当 `normalize_comments = true` 时: 30 | 31 | ```rust 32 | // 符合 33 | 34 | // Wait for the main task to return, and set the process error code 35 | // appropriately. 36 | 37 | // 符合 38 | // 在使用 `mod` 关键字定义模块时,在 `mod`之上使用 `///` 更好。 39 | 40 | /// This module contains tests 41 | mod tests { 42 | // ... 43 | } 44 | 45 | // 符合 46 | #[doc = "Example item documentation"] 47 | pub enum Foo {} 48 | ``` 49 | 50 | **【rustfmt 配置】** 51 | 52 | | 对应选项 | 可选值 | 是否 stable | 说明 | 53 | | ------ | ---- | ---- | ---- | 54 | | [`normalize_comments`](https://rust-lang.github.io/rustfmt/?#normalize_comments) | false(默认) true(推荐) | No| 将 `/**/` 注释转为 `//`| 55 | | [`normalize_doc_attributes`](https://rust-lang.github.io/rustfmt/?#normalize_doc_attributes) | false(默认) | No| 将 `#![doc]` 和 `#[doc]` 注释转为 `//!` 和 `///` | -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/P.CMT.04.md: -------------------------------------------------------------------------------- 1 | ## P.CMT.04 文件头注释包含版权说明 2 | 3 | **【描述】** 4 | 5 | 文件头(即,模块级)注释应先包含版权说明。如果文件头注释需要增加其他内容,可以在版权说明下面补充。 6 | 7 | 可以包括: 8 | 9 | 1. 文件功能说明。 10 | 2. 作者。 11 | 3. 创建日期 和 最后修改日期。 12 | 4. 注意事项。 13 | 5. 开源许可证(比如, Apache 2.0, BSD, LGPL, GPL)。 14 | 6. 其他。 15 | 16 | 版权说明格式如下: 17 | 18 | - 中文版:`版权所有(c)XXX 技术有限公司 2015-2022`。 19 | - 英文版: `Copyright (c) XXX Technologies Co.Ltd. 2015-2022. All rights reserved. Licensed under Apache-2.0.` 20 | 21 | 其内容可以进行调整,参见下面详细说明: 22 | 23 | - `2015-2022` 根据实际需要可以修改。2015是文件首次创建年份,2022是文件最后修改年份。可以只写一个创建年份,后续如果经常修改则无需修改版权声明。 24 | - 如果是内部使用,则无需增加 `All rights reserved`。 25 | - `Licensed under Apache-2.0.`,如果是开源则可以增加许可证声明。 26 | 27 | 编写版权注释时注意事项: 28 | 29 | - 版权注释应该从文件头顶部开始写。 30 | - 文件头注释首先包含“版权说明”,然后紧跟其他内容。 31 | - 可选内容应按需添加,避免空有格式没有内容的情况。 32 | - 保持统一格式,具体格式由项目或更大的范围统一制定。 33 | - 保持版面工整,换行注意对齐。 34 | 35 | **【正例】** 36 | 37 | ```rust 38 | // 符合 39 | // 版权所有(c)XXX 技术有限公司 2015-2022。 40 | 41 | // Or 42 | 43 | // 符合 44 | // Copyright (c) XXX Technologies Co.Ltd. 2015-2022. 45 | // All rights reserved. Licensed under Apache-2.0. 46 | ``` -------------------------------------------------------------------------------- /src/safe-guides/code_style/comments/P.CMT.05.md: -------------------------------------------------------------------------------- 1 | ## P.CMT.05 在注释中使用 `FIXME` 和 `TODO` 来帮助任务协作 2 | 3 | **【描述】** 4 | 5 | 通过在注释中开启 `FIXME` 和 `TODO` 可以方便协作。正式发布版本可以不做此类标注。 6 | 7 | 注意:此条目不适于使用 `rustfmt`相关配置项 `report_fixme` 和 `report_todo`,在 `rustfmt` v2.0 中已经移除这两项配置。 8 | 9 | **【正例】** 10 | 11 | ```rust 12 | // 符合 13 | // TODO(calebcartwright): consider enabling box_patterns feature gate 14 | fn annotation_type_for_level(level: Level) -> AnnotationType { 15 | match level { 16 | Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error, 17 | Level::Warning => AnnotationType::Warning, 18 | Level::Note => AnnotationType::Note, 19 | Level::Help => AnnotationType::Help, 20 | // FIXME(#59346): Not sure how to map these two levels 21 | Level::Cancelled | Level::FailureNote => AnnotationType::Error, 22 | Level::Allow => panic!("Should not call with Allow"), 23 | } 24 | } 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt.md: -------------------------------------------------------------------------------- 1 | # 2.2 格式 2 | 3 | 制定统一的编码风格,是为了提升代码的可读性,让日常代码维护和团队之间审查代码更加方便。 4 | 5 | Rust 有自动化格式化工具 rustfmt ,可以帮助开发者摆脱手工调整代码格式的工作,提升生产力。但是,rustfmt 遵循什么样的风格规范,作为开发者需要了解,在编写代码的时候可以主动按这样的风格编写。 6 | 7 | 说明: 8 | 9 | 对于 `rustfmt` 中未稳定的配置项(`Stable`为`No`),则表示该配置项不能在稳定版(Stable)Rust 中更改配置,但其默认值会在`cargo fmt`时生效。在 Nightly Rust 下则都可以自定义配置。 10 | 11 | 如需了解在稳定版 Rust 中使用未稳定配置项的方法、配置示例及其他全局配置项说明,请参阅:[Rustfmt 配置相关说明](./../Appendix/tools/rustfmt.md) 。 12 | 13 | **【注意事项】** 14 | 15 | 因为 rustfmt 工具会自动修改代码,为了确保 rustfmt 不会因为意外而改错代码,所以在使用 rustfmt 时应该注意下面两项描述: 16 | 17 | 1. 务必保证在全部把代码修改完毕且编译通过之后再执行 rustfmt 命令。 因为 rustfmt 执行过程中不会对代码进行编译,所以就不会有静态检查保护。 18 | 2. 如果是使用 IDE 或 编辑器的时候开启了自动保护功能,就不要开启自动执行 rustfmt 功能。 19 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.02.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.02 缩进使用空格而非制表符 2 | 3 | **【描述】** 4 | 5 | 缩进要使用四个空格,不要使用制表符(`\t`)代替。可以通过 IDE 或编辑器把缩进设置为四个空格。 6 | 7 | **【rustfmt 配置】** 8 | 9 | | 对应选项 | 可选值 | 是否 stable | 说明| 10 | | ------ | ---- | ---- | ---- | 11 | | [`tab_spaces`](https://rust-lang.github.io/rustfmt/#tab_spaces) | 4| yes(默认)| 缩进空格数| 12 | |[`hard_tabs`](https://rust-lang.github.io/rustfmt/#hard_tabs)| false| yes(默认)| 禁止使用tab缩进| -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.03.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.03 行间距最大宽度空一行 2 | 3 | **【描述】** 4 | 5 | 代码行之间,最小间隔 `0` 行,最大间隔`1`行。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | fn foo() { 11 | println!("a"); 12 | } 13 | // 不符合:空两行 14 | // 不符合:空两行 15 | fn bar() { 16 | println!("b"); 17 | // 不符合:空两行 18 | // 不符合:空两行 19 | println!("c"); 20 | } 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | fn foo() { 27 | println!("a"); 28 | } 29 | // 符合:空一行 30 | fn bar() { 31 | println!("b"); 32 | println!("c"); 33 | } 34 | ``` 35 | 36 | 或者 37 | 38 | ```rust 39 | fn foo() { 40 | println!("a"); 41 | } 42 | fn bar() { 43 | println!("b"); 44 | // 符合:空一行 45 | println!("c"); 46 | } 47 | 48 | ``` 49 | 50 | **【rustfmt 配置】** 51 | 52 | | 对应选项 | 可选值 | 是否 stable | 说明 | 53 | | ------ | ---- | ---- | ---- | 54 | | [`blank_lines_lower_bound`](https://rust-lang.github.io/rustfmt/?#blank_lines_lower_bound) | 0(默认) | No| 不空行| 55 | |[`blank_lines_upper_bound`](https://rust-lang.github.io/rustfmt/?#blank_lines_upper_bound)| 1(默认)| No | 最大空一行| -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.13.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.13 具名结构体字段初始化时不要省略字段名 2 | 3 | **【描述】** 4 | 5 | 因为本规则依赖于rustfmt,而rustfmt会根据相应配置项对代码进行自动更改,为了确保不会因为rustfmt配置项的更改而导致代码错误,请在遵循rustfmt使用注意事项的基础上遵循本规则: 6 | 7 | 1. 省略字段名的时候需要注意变量名和字段名保持一致。 8 | 2. 变量名和字段名不一致的情况下,不要省略字段名。 9 | 10 | > 注意:如果将 rustfmt 默认配置 `use_field_init_shorthand`改为`true`时,有可能会发生代码被修改错误的情况。 11 | 12 | 13 | **【反例】** 14 | 15 | ```rust 16 | struct Foo { 17 | a: u32, // 注意这里是 a 18 | y: u32, 19 | z: u32, 20 | } 21 | 22 | fn main() { 23 | let x = 1; 24 | let y = 2; 25 | let z = 3; 26 | // 不符合: 如果允许省略字段名,并且rustfmt 配置 `use_field_init_shorthand`改为`true`时, 27 | // 下面代码中字段`a`就会被rustfmt删除,变为 `Foo{x, y, z}`,从而造成错误 28 | // rustfmt 无法检查这个错误,但是编译时能检查出来,所以要遵循rustfmt使用注意事项就不会出问题 29 | let a = Foo { a: x, y, z }; 30 | } 31 | ``` 32 | 33 | **【正例】** 34 | 35 | ```rust 36 | 37 | struct Foo { 38 | a: u32, 39 | y: u32, 40 | z: u32, 41 | } 42 | 43 | fn main() { 44 | let x = 1; 45 | let y = 2; 46 | let z = 3; 47 | // 符合 48 | let a = Foo { a: x, y: y, z: z }; 49 | } 50 | ``` 51 | 52 | **【rustfmt 配置】** 53 | 54 | | 对应选项 | 可选值 | 是否 stable | 说明 | 55 | | ------ | ---- | ---- | ---- | 56 | | [`use_field_init_shorthand`](https://rust-lang.github.io/rustfmt/?#use_field_init_shorthand) | false(默认) | Yes |具名结构体字段初始化不能省略字段名| 57 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.14.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.14 extern 外部函数需要显式指定 C-ABI 2 | 3 | **【描述】** 4 | 5 | 当使用 `extern` 指定外部函数时,建议显式指定 `C-ABI`。 6 | 7 | 虽然 `extern` 不指定的话默认就是 `C-ABI`,但是 Rust 语言显式指定是一种约定俗成。 8 | 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | // 不符合:不要省略 C-ABI 指定 14 | extern { 15 | pub static lorem: c_int; 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | // 符合 23 | extern "C" { 24 | pub static lorem: c_int; 25 | } 26 | 27 | extern "Rust" { 28 | type MyType; 29 | fn f(&self) -> usize; 30 | } 31 | ``` 32 | 33 | **【rustfmt 配置】** 34 | 35 | | 对应选项 | 可选值 | 是否 stable | 说明 | 36 | | ------ | ---- | ---- | ---- | 37 | | [`force_explicit_abi`](https://rust-lang.github.io/rustfmt/?#force_explicit_abi) | true(默认) | Yes| extern 外部函数总是要指定 ABI | 38 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.15.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.15 解构元组的时候允许使用`..`来指代剩余元素 2 | 3 | **【描述】** 4 | 5 | `rustfmt` 可以由 `condense_wildcard_suffixes` 配置项来格式化此规则,其默认选项是 false,表示不允许 解构元组的时候使用`..`来指代剩余元素,所以需要修改默认配置项的值为 `true` 才符合规范。 6 | 7 | **【反例】** 8 | 9 | 默认情况下,rustfmt 不会自动更改代码,会保留原来的写法。 10 | 11 | ```rust 12 | fn main() { 13 | // 不符合: 应该使用`..` 14 | let (lorem, ipsum, _, _) = (1, 2, 3, 4); 15 | let (lorem, _,ipsum, _, _) = (1, 2, 3, 4, 5); 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | 设置 `condense_wildcard_suffixes = true` 时,会强行更改代码为下面形式。 22 | 23 | ```rust 24 | fn main() { 25 | // 符合 26 | let (lorem, ipsum, ..) = (1, 2, 3, 4); 27 | let (lorem, _,ipsum, ..) = (1, 2, 3, 4, 5); 28 | } 29 | ``` 30 | 31 | **【rustfmt 配置】** 32 | 33 | | 对应选项 | 可选值 | 是否 stable | 说明 | 34 | | ------------------------------------------------------------ | --------------------------- | ----------- | -------------------------------------------- | 35 | | [`condense_wildcard_suffixes`](https://rust-lang.github.io/rustfmt/?#condense_wildcard_suffixes) | false(默认) true (推荐) | No | 解构元组的时候是否允许使用`..`来指代剩余元素 | 36 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/fmt/P.FMT.16.md: -------------------------------------------------------------------------------- 1 | ## P.FMT.16 不要将派生宏中多个不相关的特质合并为同一行 2 | 3 | **【描述】** 4 | 5 | 不要将派生宏(Derive)中多个特质(trait)合并为同一行,这样可以增加代码可读性,明确语义。 6 | 7 | `rustfmt` 配置项 `merge_derives` 用于匹配此格式,其默认值是让派生宏中多个特质在同一行,所以需要修改其默认值。 8 | 9 | 说明: `rustfmt` 并不会识别哪些特质相关,所以需要开发者手工指定好。 10 | 11 | **【反例】** 12 | 13 | 当使用默认设置 `merge_derives = true` 时,不符合。 14 | 15 | ```rust 16 | // 不符合:不相关的特质放到一行 17 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 18 | pub enum Foo {} 19 | ``` 20 | 21 | **【正例】** 22 | 23 | 修改默认设置 `merge_derives = false`,符合。 24 | 25 | ```rust 26 | // 符合 27 | #[derive(Eq, PartialEq)] 28 | #[derive(Debug)] 29 | #[derive(Copy, Clone)] 30 | pub enum Foo {} 31 | ``` 32 | 33 | **【rustfmt 配置】** 34 | 35 | | 对应选项 | 可选值 | 是否 stable | 说明 | 36 | | ------------------------------------------------------------ | -------------------------- | ----------- | -------------------------------- | 37 | | [`merge_derives`](https://rust-lang.github.io/rustfmt/?#merge_derives) | true(默认) false(推荐) | Yes | 是否将多个 Derive 宏合并为同一行 | 38 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming.md: -------------------------------------------------------------------------------- 1 | # 2.1 命名 2 | 3 | 好的命名风格能让我们快速地了解某个名字代表的含义(类型、变量、函数、常量、宏等),甚至能凸显其在整个代码上下文中的语义。命名管理对提升代码的可读性和维护性相当重要。 4 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.01.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.01 同一个crate中标识符的命名规则应该使用统一的词序 2 | 3 | **【描述】** 4 | 5 | 具体选择什么样的词序并不重要,但务必要保证同一个 crate 内词序的一致性。 6 | 若提供与标准库中相似功能的东西时,也要与标准库名称的词性顺序一致. 7 | 8 | > 拿错误类型来举个例子: 9 | > 10 | > 当crate中类型名称都按照 **动词-宾语-error** 这样的顺序来命名错误类型时,如果要增加新的错误类型,则也需要按同样的词序来增加。 11 | 12 | 以下是来自标准库的处理错误的一些类型示例: 13 | 14 | - [`JoinPathsError`](https://doc.rust-lang.org/std/env/struct.JoinPathsError.html) 15 | - [`ParseBoolError`](https://doc.rust-lang.org/std/str/struct.ParseBoolError.html) 16 | - [`ParseCharError`](https://doc.rust-lang.org/std/char/struct.ParseCharError.html) 17 | - [`ParseFloatError`](https://doc.rust-lang.org/std/num/struct.ParseFloatError.html) 18 | - [`ParseIntError`](https://doc.rust-lang.org/std/num/struct.ParseIntError.html) 19 | - [`RecvTimeoutError`](https://doc.rust-lang.org/std/sync/mpsc/enum.RecvTimeoutError.html) 20 | - [`StripPrefixError`](https://doc.rust-lang.org/std/path/struct.StripPrefixError.html) 21 | 22 | 如果你想新增和标准库相似的错误类型,比如“解析地址错误”类型,为了保持词性一致,应该使用`ParseAddrError` 名称,而不是`AddrParseError`。 23 | 24 | > 说明:现在标准库文档中 net模块解析地址错误类型是 `AddrParseError`,其实和标准库中大部分错误类型遵循的 "动-宾-Error" 词序没有保持一致,所以它是一个特例。 25 | 26 | **【反例】** 27 | ```rust 28 | // 不符合:与标准库错误类型词序 "动-宾-Error" 不一致,应该为 ParseAddrError 29 | struct AddrParseError {} 30 | ``` 31 | 32 | **【正例】** 33 | ```rust 34 | // 符合: 与标准库错误类型一致 35 | struct ParseAddrError{} 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.02.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.02 为 cargo feature 命名时不应含有无意义的占位词 2 | 3 | **【描述】** 4 | 5 | 给 [Cargo feature](http://doc.crates.io/manifest.html#the-features-section) 命名时,不应带有无实际含义的的词语,比如使用`abc`命名来替代 `use-abc` 或 `with-abc`。 6 | 7 | 这条原则经常出现在对 Rust 标准库进行 [可选依赖(optional-dependency)](https://doc.rust-lang.org/cargo/reference/features.html#optional-dependencies) 配置的 crate 上。 8 | 9 | 并且 Cargo 要求 features 应该是相互叠加的,所以像 `no-abc` 这种负向的 feature 命名实际上并不正确。 10 | 11 | **【反例】** 12 | 13 | ```toml 14 | # In Cargo.toml 15 | 16 | 17 | [features] 18 | // 不符合 19 | default = ["use-std"] 20 | std = [] 21 | // 不符合 22 | no-abc=[] 23 | ``` 24 | 25 | ```rust,ignored 26 | // In lib.rs 27 | 28 | #![cfg_attr(not(feature = "use-std"), no_std)] 29 | ``` 30 | 31 | **【正例】** 32 | 33 | 最简洁且正确的做法是: 34 | 35 | ```toml 36 | # In Cargo.toml 37 | 38 | [features] 39 | // 符合 40 | default = ["std"] 41 | std = [] 42 | ``` 43 | 44 | ```rust,ignored 45 | // In lib.rs 46 | 47 | #![cfg_attr(not(feature = "std"), no_std)] 48 | ``` 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.03.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.03 标识符命名应该符合阅读习惯 2 | 3 | **【描述】** 4 | 5 | 标识符的命名要清晰、明了,有明确含义,容易理解。符合英文阅读习惯的命名将明显提高代码可读性。 6 | 7 | 一些好的实践包括但不限于: 8 | 9 | - 使用正确的英文单词并符合英文语法,不要使用拼音 10 | - 仅使用常见或领域内通用的单词缩写 11 | - 布尔型变量或函数避免使用否定形式,双重否定不利于理解 12 | - 不要使用 Unicode 标识符 13 | 14 | **【反例】** 15 | 16 | ```rust 17 | // 不符合: 使用拼音 18 | let ming: &str = "John"; 19 | let xing: &str = "Smith"; 20 | // 不符合: 含义不明确 21 | const ERROR_NO_1: u32 = 336; 22 | const ERROR_NO_2: u32 = 594; 23 | // 不符合:函数名字表示的函数作用不明了 24 | fn not_number(s:&str) -> bool {/* ... */} 25 | ``` 26 | 27 | **【正例】** 28 | 29 | ```rust 30 | // 符合 31 | let first_name: &str = "John"; 32 | let last_name: &str = "Smith"; 33 | const ERR_DIR_NOT_SUPPORTED: u32 = 336; 34 | const ERR_DVER_CANCEL_TIMEOUT: u32 = 594; 35 | // 符合 36 | fn is_number(s:&str) -> bool {/* ... */} 37 | ``` -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.04.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.04 作用域越大命名越精确,反之应简短 2 | 3 | **【描述】** 4 | 5 | 1. 对于全局函数、全局变量、宏、类型名、枚举命名,应当精确描述并全局唯一。 6 | 2. 对于函数局部变量,或者结构体、枚举中的成员变量,在其命名能够准确表达含义的前提下,应该尽量简短,避免冗余信息重复描述。 7 | 8 | **【反例】** 9 | 10 | ```rust 11 | // 不符合:描述不精确 12 | static GET_COUNT: i32 = 42; 13 | 14 | // 不符合:信息冗余 15 | enum WebEvent { 16 | PageLoadEvent, 17 | PageUnloadEvent, 18 | KeyPressEvent(char), 19 | PasteEvent(String), 20 | ClickEvent { x: i64, y: i64 }, 21 | } 22 | 23 | // 不符合:信息冗余 24 | type MaskSize = u16; 25 | pub struct HeaderMap { 26 | mask: MaskSize, 27 | } 28 | ``` 29 | 30 | **【正例】** 31 | 32 | ```rust 33 | // 符合 34 | static MAX_THREAD_COUNT: i32 = 42; 35 | 36 | // 符合: 上下文信息已经知道它是 Event 37 | enum WebEvent { 38 | PageLoad, 39 | PageUnload, 40 | KeyPress(char), 41 | Paste(String), 42 | Click { x: i64, y: i64 }, 43 | } 44 | 45 | // 符合:在使用它的地方自然就知道是描述谁的大小 46 | type Size = u16; 47 | pub struct HeaderMap { 48 | mask: Size, 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.07.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.07 避免使用语言内置保留字、关键字、内置类型和`trait`等特殊名称 2 | 3 | **【描述】** 4 | 5 | 命名必须要避免使用语言内置的保留字、关键字、内置类型和`trait`等特殊名称。 具体可以参考[The Rust Reference-Keywords](https://doc.rust-lang.org/stable/reference/keywords.html)。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 不符合:Rust 内置了 Sized trait 11 | type Sized = u16; 12 | 13 | fn main() { 14 | // 不符合:try 为保留关键字 15 | let try = 1; 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | // 符合 23 | type Size = u16; 24 | 25 | fn main() { 26 | // 符合 27 | let tried = 1; 28 | } 29 | ``` 30 | 31 | **【例外】** 32 | 33 | 在一些特定场合,比如对接遗留数据库中的字段和Rust关键字冲突: 34 | 35 | ```rust 36 | 37 | struct SomeTable{ 38 | // 使用 `r#`+type 来解决这种问题 39 | r#type: String 40 | } 41 | 42 | ``` 43 | 44 | 或者当序列化为 json 或 proto 时,存在成员为关键字,则可以通过相关库提供的功能来使用: 45 | 46 | ```rust 47 | pub struct UserRepr { 48 | // ... 49 | #[serde(rename="self")] 50 | pub self_: Option, 51 | // ... 52 | } 53 | ``` -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.08.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.08 避免在变量的命名中添加类型标识 2 | 3 | **【描述】** 4 | 5 | 因为 Rust 语言类型系统崇尚显式的哲学,所以不需要在变量命名中也添加关于类型的标识。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | let account_bytes: Vec = read_some_input(); // 不符合:account 的类型很清楚,没必要在命名中加 `_bytes` 11 | let account_str = String::from_utf8(account_bytes)?; // 不符合:account 的类型很清楚,没必要在命名中加 `_str` 12 | let account: Account = account_str.parse()?; // 不符合:account 的类型很清楚,没必要在命名中加 `_str` 13 | ``` 14 | 15 | **【正例】** 16 | 17 | ```rust 18 | let account: Vec = read_some_input(); // 符合 19 | let account = String::from_utf8(account)?; // 符合 20 | let account: Account = account.parse()?; // 符合 21 | ``` 22 | -------------------------------------------------------------------------------- /src/safe-guides/code_style/naming/P.NAM.09.md: -------------------------------------------------------------------------------- 1 | ## P.NAM.09 定义全局静态变量时需加前缀`G_`以便和常量有所区分 2 | 3 | **【描述】** 4 | 5 | 为了提升代码可读性和可维护性,有必要将常量的命名和全局静态变量加以区分。所以在定义全局静态变量时,需要以前缀`G_`命名。 6 | 7 | 8 | **【反例】** 9 | 10 | ```rust 11 | // 不符合: 无法通过命名直接区分常量和静态变量 12 | static EVENT: [i32;5]=[1,2,3,4,5]; 13 | const MAGIC_NUM: i32 = 65; 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | // 符合 20 | static G_EVENT: [i32;5]=[1,2,3,4,5]; 21 | const MAGIC_NUM: i32 = 65; 22 | ``` 23 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice.md: -------------------------------------------------------------------------------- 1 | # 3. 编程实践 2 | 3 | 编码实践相关原则和规则,有助于编写更地道更安全的 Rust 代码。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/async-await.md: -------------------------------------------------------------------------------- 1 | # 3.18 异步编程 2 | 3 | `async / await` 是 Rust 语言用于编写像同步代码一样的异步函数的内置工具。`async` 将一个代码块转化为一个实现了名为 `Future` 的特质 (trait) 4 | 的状态机。虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的 `Future` 将让出线程控制权,允许其他 `Future` 运行。 5 | 6 | Rust 异步编程需要依赖于异步运行时,生产环境中比较推荐的开源异步运行时是 [Tokio](https://github.com/tokio-rs/tokio)。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/async-await/G.ASY.01.md: -------------------------------------------------------------------------------- 1 | ## G.ASY.01 在 `async` 块或函数中调用 `async` 函数或闭包请不要忘记添加`.await` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在此条件下 `.await` 语句通常为必须的。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | async fn foo() {} 13 | 14 | fn bar() { 15 | let x = async { 16 | foo() // 不符合 17 | }; 18 | } 19 | ``` 20 | 21 | **【正例】** 22 | 23 | ```rust 24 | async fn foo() {} 25 | 26 | fn bar() { 27 | let x = async { 28 | foo().await // 符合 29 | }; 30 | } 31 | ``` 32 | 33 | 34 | **【Lint 检测】** 35 | 36 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 37 | | ---------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 38 | | [async_yields_async](https://rust-lang.github.io/rust-clippy/master/#async_yields_async) | yes | no | correctness | deny | 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/async-await/G.ASY.04.md: -------------------------------------------------------------------------------- 1 | ## G.ASY.04 避免定义不必要的异步函数 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 如果一个异步函数内部没有任何异步代码,相比一个同步函数,它会产生额外的调用成本。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | #[warn(clippy::unused_async)] 14 | async fn add(value: i32) -> i32 { 15 | value + 1 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | // 符合 23 | #[warn(clippy::unused_async)] 24 | fn add(value: i32) -> i32 { 25 | value + 1 26 | } 27 | ``` 28 | 29 | **【Lint 检测】** 30 | 31 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 32 | | ---------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 33 | | [unused_async](https://rust-lang.github.io/rust-clippy/master/#unused_async) | yes | no | pedantic | allow | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/async-await/G.ASY.05.md: -------------------------------------------------------------------------------- 1 | ## G.ASY.05 避免在异步处理过程中包含阻塞操作 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 避免在异步编程中使用阻塞操作。 8 | 9 | **【反例】** 10 | 11 | 不要在异步流程中使用阻塞操作函数 12 | 13 | ```rust 14 | use std::error::Error; 15 | use std::{fs, io}; 16 | 17 | async fn read_file() -> Result { 18 | fs::read_to_string("test.txt") // 不符合 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | 使用异步运行时,如tokio提供的非阻塞函数 25 | 26 | ```rust 27 | use tokio::fs; 28 | 29 | async fn read_file() -> std::io::Result<()> { 30 | let _ = fs::read_to_string("test.txt").await?; // 符合 31 | Ok(()) 32 | } 33 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/async-await/P.ASY.01.md: -------------------------------------------------------------------------------- 1 | ## P.ASY.01 异步编程并不适合所有场景,计算密集型场景应该考虑同步编程 2 | 3 | **【描述】** 4 | 5 | 异步编程适合 I/O 密集型应用,如果是计算密集型场景应该考虑使用同步编程。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo.md: -------------------------------------------------------------------------------- 1 | # 3.15 包管理 2 | 3 | Cargo 不仅仅是包管理,它还是一个 Workflow 工具。这一节包含 Cargo 和 Crate 相关内容。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/G.CAR.01.md: -------------------------------------------------------------------------------- 1 | ## G.CAR.01 当项目是可执行程序而非库时,建议使用 `src/main.rs` 和 `src/lib.rs` 模式 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | `crate` 结构类似于: 8 | 9 | ```text 10 | src/ 11 | -- lib.rs 12 | -- main.rs 13 | ``` 14 | 15 | 或 16 | 17 | ```text 18 | src/ 19 | -- lib.rs 20 | bin/ 21 | -- main.rs 22 | ``` 23 | 24 | 这样的好处有: 25 | 26 | 1. 便于单元测试。 27 | 2. 有利于面向接口思考,让代码架构和逻辑更加清晰。 28 | 29 | 若编写的可执行程序比较复杂,在 `main.rs` 里需要依赖太多东西时,那就需要创建 Workspace 把 `main.rs` 独立为一个 crate,而在这个 crate 内也没有必要再拆分为 `main` 和 `lib` 了。 30 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/G.CAR.02.md: -------------------------------------------------------------------------------- 1 | ## G.CAR.02 Crate 的 Cargo.toml 中应该包含必要的元信息 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在 Cargo.toml 中应该包含必要的元信息,以便使用者知道它的作用。 8 | 此外,若要将 `crate` 发布到 crates.io 上的话,这些信息也是必须的。可参考 The Cargo Book 中的[相关介绍](https://doc.rust-lang.org/cargo/reference/manifest.html)。 9 | 10 | **【反例】** 11 | 12 | ```toml 13 | # 不符合:此 `Cargo.toml` 缺失介绍(description)项。无法发布到 crates.io。 14 | [package] 15 | name = "clippy" 16 | version = "0.0.212" 17 | repository = "https://github.com/rust-lang/rust-clippy" 18 | readme = "README.md" 19 | license = "MIT OR Apache-2.0" 20 | keywords = ["clippy", "lint", "plugin"] 21 | categories = ["development-tools", "development-tools::cargo-plugins"] 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```toml 27 | # 符合:此 `Cargo.toml` 包含必要元信息。 28 | [package] 29 | name = "clippy" 30 | version = "0.0.212" 31 | description = "A bunch of helpful lints to avoid common pitfalls in Rust" 32 | repository = "https://github.com/rust-lang/rust-clippy" 33 | readme = "README.md" 34 | license = "MIT OR Apache-2.0" 35 | keywords = ["clippy", "lint", "plugin"] 36 | categories = ["development-tools", "development-tools::cargo-plugins"] 37 | ``` 38 | 39 | **【Lint 检测】** 40 | 41 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 42 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 43 | | [cargo_common_metadata](https://rust-lang.github.io/rust-clippy/master/#cargo_common_metadata) | yes | no | cargo | allow | 44 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/G.CAR.03.md: -------------------------------------------------------------------------------- 1 | ## G.CAR.03 Feature 命名应该避免否定式或多余的前后缀 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Feature 命名应该避免出现 `no-` 或 `not-` 之类的否定前缀,或诸如 `use-`,`with-` 前缀或 `-support`后缀。Feature 的目的是正向的,可选的特性,使用否定式命名和它的目的背道而驰。 8 | 9 | **【反例】** 10 | 11 | ```toml 12 | [features] 13 | default = ["no-abc", "with-def", "ghi-support"] 14 | no-abc = [] # 不符合:命名否定式 15 | with-def = [] # 不符合:多余前缀 16 | ghi-support = [] # 不符合:多余后缀 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```toml 22 | # 符合 23 | [features] 24 | default = ["abc", "def", "ghi"] 25 | abc = [] 26 | def = [] 27 | ghi = [] 28 | ``` 29 | 30 | **【Lint 检测】** 31 | 32 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 33 | | -------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 34 | | [negative_feature_names](https://rust-lang.github.io/rust-clippy/master/#negative_feature_names) | yes | no | cargo | allow | 35 | | [redundant_feature_names](https://rust-lang.github.io/rust-clippy/master/#redundant_feature_names) | yes | no | cargo | allow | 36 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/G.CAR.04.md: -------------------------------------------------------------------------------- 1 | ## G.CAR.04 Cargo.toml 中依赖包版本不应使用通配符 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 依赖的包必须指定具体的语义版本。关于语义版本说明参见:[The Cargo Book: SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html)。 8 | 9 | > 使用 Clippy 需要设置 `#[warn(clippy::wildcard_dependencies)]`。 10 | 11 | **【反例】** 12 | 13 | ```toml 14 | [dependencies] 15 | regex = "*" # 不符合:避免项目依赖因为上游更新而自动更新 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```toml 21 | [dependencies] 22 | regex = "1.5" # 符合 23 | ``` 24 | 25 | **【Lint 检测】** 26 | 27 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 28 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 29 | | [wildcard_dependencies](https://rust-lang.github.io/rust-clippy/master/#wildcard_dependencies) | yes | no | cargo | allow | 30 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/P.CAR.01.md: -------------------------------------------------------------------------------- 1 | ## P.CAR.01 应该尽量把项目划分为合理的 crate 组合 2 | 3 | **【描述】** 4 | 5 | 将整个项目按一定逻辑划分为合理的 crate,在工程方面有利于组件化。并且 crate 是 Rust 的编译单元,也有助于提升编译速度。 6 | 7 | 但需要注意,crate 之间的依赖关系应该是单向的,避免相互依赖的情况。 8 | 9 | 但 Rust 中编译时间、性能、编译大小之间,在考虑优化的时候也是需要权衡的。 10 | 11 | 内联是优化的关键,当编译单元越大,内联优化效果就越好。所以需要权衡 crate 划分的粒度。 12 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/P.CAR.02.md: -------------------------------------------------------------------------------- 1 | ## P.CAR.02 不要滥用 Features 2 | 3 | **【描述】** 4 | 5 | Rust 的 features 提供了方便的条件编译功能。从软件工程来说,features 适合应用于可选功能。 6 | 7 | 在使用 features 的时候,应该考虑到底是不是真的需要 features。 8 | 9 | 滥用 features 会带来额外的测试和静态检查的难度,需要保证不同 features 下的测试覆盖和静态检查情况。 10 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/P.CAR.03.md: -------------------------------------------------------------------------------- 1 | ## P.CAR.03 使用 `cargo features` 来代替 `--cfg` 条件编译参数 2 | 3 | **【描述】** 4 | 5 | `cargo features` 为 Rust 原生的条件编译,可用于代替 `--cfg` 参数且兼容性更好。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/cargo/P.CAR.04.md: -------------------------------------------------------------------------------- 1 | ## P.CAR.04 宜使用 `cfg!` 来代替 `#[cfg]` 2 | 3 | **【描述】** 4 | 5 | `cfg!` 和正常代码一样,会检查全部函数逻辑,而 `#[cfg]` 是条件编译,则会跳过一些 Dead Code。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/code-generation.md: -------------------------------------------------------------------------------- 1 | # 代码生成 2 | 3 | Rust 中代码生成的方式包括宏 与 `build.rs` 两种方式。关于宏,有独立的规范章节,本章节规范内容包括: 4 | 5 | - `build.rs` 使用规范 6 | - 代码生成相关其他规范 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/code-generation/P.CGN.01.md: -------------------------------------------------------------------------------- 1 | # P.CGN.01 代码生成要按情况选择使用过程宏还是 `build.rs` 2 | 3 | **【描述】** 4 | 5 | 用过程宏生进行代码生成,比如生成新类型或函数,有一个缺点就是:IDE 无法识别它们,影响开发体验。 6 | 7 | 但是使用 `build.rs` 生成的代码,对 IDE 更友好。 8 | 9 | 不过随着 IDE 的增强,过程宏以后应该也能变得更加 IDE 友好。 10 | 11 | 建议按应用场景选择: 12 | 13 | - `build.rs` 一般用于根据外部文件生成代码的场景。比如根据 `C` 头文件生成 Rust 绑定,或者根据 `proto` 文件生成相应的 Rust 类型等,供开发者直接使用。 14 | - 过程宏一般用于消除样例式代码,提升库使用者的开发体验。 15 | 16 | **【正例】** 17 | 18 | `build.rs` 把 `tonic` 生成的代码直接放在 `src` 目录 (生成的代码文件应该在 .gitignore 中忽略版本管理),这样 IDE 能够识别它们使自动完成能够工作,提高开发效率。 19 | 20 | ```rust 21 | fn main() -> Result<(), Box> { 22 | tonic_build::configure() 23 | .out_dir("src") 24 | .compile( 25 | &["proto/helloworld/helloworld.proto"], 26 | &["proto/helloworld"], 27 | )?; 28 | println!("cargo:rerun-if-changed=proto"); 29 | } 30 | ``` 31 | 32 | `tarpc`的`service`宏会生成一个新的`WorldClient`类型,IDE完全无法识别。 33 | 34 | ```rust 35 | #[tarpc::service] 36 | trait World { 37 | async fn hello(name: String) -> String; 38 | } 39 | 40 | let (client_transport, server_transport) = tarpc::transport::channel::unbounded(); 41 | let mut client = WorldClient::new(client::Config::default(), client_transport).spawn(); 42 | ``` 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/code-generation/P.CGN.02.md: -------------------------------------------------------------------------------- 1 | # P.CGN.02 `build.rs` 生成的代码要保证没有任何警告 2 | 3 | **【描述】** 4 | 5 | `build.rs` 生成的代码(codegen),要通过或忽略 clippy 检查,不要让库的使用者或应用用户自行忽略 6 | 7 | codegen 库要保证生成的代码应该非常干净没有任何警告,不应该让库的使用者去处理生成代码中的警告。 8 | 9 | **【反例】** 10 | 11 | lalrpop v0.19.6 生成的代码有几百个 clippy 警告,"淹没"了用户自己代码的 clippy 警告 12 | 13 | ``` 14 | warning: using `clone` on type `usize` which implements the `Copy` trait 15 | --> /home/w/temp/my_parser/target/debug/build/my_parser-dd96f436ee76c58d/out/my_parser.rs:182148:21 16 | | 17 | 182148 | let __end = __start.clone(); 18 | | ^^^^^^^^^^^^^^^ help: try removing the `clone` call: `__start` 19 | ``` 20 | 21 | 使得 lalrpop 库的使用者必须手动给生成的模块代码加上 allow clippy,给使用者带来不便 22 | 23 | ```rust 24 | lalrpop_mod!( 25 | #[allow(clippy::all)] 26 | my_parser 27 | ); 28 | ``` 29 | 30 | **【正例】** 31 | 32 | tonic-build 生成的 rs 会通过 allow 忽略掉 clippy 警告 33 | 34 | ```rust 35 | pub mod peer_communication_client { 36 | #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 37 | use tonic::codegen::*; 38 | ``` 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/collections.md: -------------------------------------------------------------------------------- 1 | # 3.8 集合类型 2 | 3 | Rust 中的集合类型包括四大类: 4 | 5 | - 线性序列: [`Vec`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html), [`VecDeque`](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html), [`LinkedList`](https://doc.rust-lang.org/stable/std/collections/struct.LinkedList.html) 6 | - 映射集:[`HashMap`](https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html), [`BTreeMap`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html) 7 | - 集合: [`HashSet`](https://doc.rust-lang.org/stable/std/collections/hash_set/struct.HashSet.html), [`BTreeSet`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeSet.html) 8 | - 其他: [`BinaryHeap`](https://doc.rust-lang.org/stable/std/collections/struct.BinaryHeap.html) 9 | 10 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/collections/G.CLT.01.md: -------------------------------------------------------------------------------- 1 | ## G.CLT.01 非必要情况下,不要使用`LinkedList`,而用`Vec`或`VecDeque`代替 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 一般情况下,有 `Vec`和`VecDeque` 性能更好。`LinkedList` 存在内存浪费,缓存局部性(Cache Locality)比较差,无法更好地利用 CPU 缓存机制,性能很差。 8 | 9 | 只有在有大量的 列表 拆分 和 合并 操作时,才真正需要链表,因为链表允许你只需操作指针而非复制数据来完成这些操作。 10 | 11 | **【Lint 检测】** 12 | 13 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 14 | | ------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 15 | | [linkedlist](https://rust-lang.github.io/rust-clippy/master/#linkedlist) | yes | no | pedantic | allow | 16 | 17 | 该 lint 对应 `clippy.toml` 配置项: 18 | 19 | ```toml 20 | # 如果函数是被导出的 API,则该 lint 不会被触发,是防止 lint 建议对 API 有破坏性的改变。默认为 true 21 | avoid-breaking-exported-api = true 22 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/collections/P.CLT.01.md: -------------------------------------------------------------------------------- 1 | ## P.CLT.01 创建HashMap、VecDeque时,可以预先分配大约足够的容量来避免后续操作中产生多次分配 2 | 3 | **【描述】** 4 | 5 | 预分配足够的容量,避免后续内存分配,可以提升代码性能。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | use std::collections::HashMap; 11 | use std::collections::VecDeque; 12 | 13 | fn main() { 14 | 15 | // 不符合 16 | let mut map = HashMap::new(); 17 | map.insert("a", 1); 18 | map.insert("b", 2); 19 | map.insert("c", 3); 20 | println!("{:#?}", map); 21 | 22 | // 不符合 23 | let mut deque = VecDeque::new(); 24 | deque.push_back(1); 25 | deque.push_back(2); 26 | deque.push_back(3); 27 | println!("{:#?}", deque); 28 | } 29 | ``` 30 | 31 | **【正例】** 32 | 33 | ```rust 34 | use std::collections::HashMap; 35 | use std::collections::VecDeque; 36 | 37 | fn main() { 38 | 39 | // 符合 40 | let mut map = HashMap::with_capacity(3); 41 | map.insert("a", 1); 42 | map.insert("b", 2); 43 | map.insert("c", 3); 44 | println!("{:#?}", map); 45 | 46 | // 符合 47 | let mut deque = VecDeque::with_capacity(3); 48 | deque.push_back(1); 49 | deque.push_back(2); 50 | deque.push_back(3); 51 | println!("{:#?}", deque); 52 | } 53 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts.md: -------------------------------------------------------------------------------- 1 | # 3.1 常量 2 | 3 | 在 Rust 中,常量有两种用途: 4 | 5 | - 编译时常量(Compile-time constants) 6 | - 编译时求值 (CTEF, compile-time evaluable functions) 7 | 8 | 常量命名风格指南请看 [编码风格-命名](../code_style/naming.md) 9 | 10 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts/G.CNS.01.md: -------------------------------------------------------------------------------- 1 | ## G.CNS.01 对于科学计算中涉及浮点数近似值的常量宜使用预定义常量 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust标准库中已经提供了一些特殊常量的定义,其精确度通常会比开发者自行定义的高,所以若考虑数值精确度时则宜使用标准库已定义的特殊常量。 8 | 9 | 这些特殊常量都可以在标准库中找到,例如[std::f32::consts](https://doc.rust-lang.org/std/f32/consts/index.html) 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | let x = 3.14; // 不符合:自定义 Pi 15 | let y = 1_f64 / x; // 不符合 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | let x = std::f32::consts::PI; // 符合 22 | let y = std::f64::consts::FRAC_1_PI; // 符合 23 | ``` 24 | 25 | **【Lint 检测】** 26 | 27 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 28 | | ------ | ---- | --------- | ------ | ------ | 29 | | [approx_constant](https://rust-lang.github.io/rust-clippy/master/#approx_constant) | yes| no | Correctness | deny | 30 | 31 | 该 Lint 默认为 `deny`,但在某些场景下,可以设置为`allow`,`#![allow(clippy::approx_constant)]`。 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts/G.CNS.02.md: -------------------------------------------------------------------------------- 1 | ## G.CNS.02 不应断言常量布尔类型 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 此类语句会被编译器优化掉。最好直接使用 `panic!` 或 `unreachable!`代替。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | const B: bool = false; 14 | assert!(B); 15 | assert!(true); 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | // 符合 22 | panic!("something"); 23 | ``` 24 | 25 | **【例外】** 26 | 27 | 该示例需要维护一个常量的不变性,确保它在未来修改时不会被无意中破坏。类似于 [static_assertions](https://docs.rs/static_assertions/1.1.0/static_assertions/) 的作用。 28 | 29 | ```rust 30 | #![allow(clippy::assertions_on_constants)] 31 | const MIN_OVERFLOW: usize = 8192; 32 | const MAX_START: usize = 2048; 33 | const MAX_END: usize = 2048; 34 | const MAX_PRINTED: usize = MAX_START + MAX_END; 35 | assert!(MAX_PRINTED < MIN_OVERFLOW); 36 | ``` 37 | 38 | **【Lint 检测】** 39 | 40 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 41 | | ------ | ---- | --------- | ------ | ------ | 42 | | [assertions_on_constants](https://rust-lang.github.io/rust-clippy/master/#assertions_on_constants) | yes| no | Style | warn | 43 | 44 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts/G.CNS.03.md: -------------------------------------------------------------------------------- 1 | ## G.CNS.03 不应将内部可变性容器声明为常量 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 由于常量有内联的特性。若将一个内容可变容器声明为常量,那么在引用它的时候同样会新建一个实例,这样会破坏内容可变容器的使用目的, 8 | 所以需要将它的值存储为静态(static)或者直接将其定义为静态。 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; 14 | const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); 15 | 16 | // 不符合 17 | CONST_ATOM.store(6, SeqCst); // 此处相当于新建了一个atomic实例,所以原容器内容并未改变 18 | assert_eq!(CONST_ATOM.load(SeqCst), 12); // 仍为12,因为这两行的CONST_ATOM为不同实例 19 | 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; 26 | const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); 27 | 28 | // 符合 29 | static STATIC_ATOM: AtomicUsize = CONST_ATOM; 30 | STATIC_ATOM.store(9, SeqCst); 31 | assert_eq!(STATIC_ATOM.load(SeqCst), 9); // 使用`static`, 故上下文的STATIC_ATOM皆指向同一个实例 32 | 33 | // 符合: 或直接声明为static 34 | static ANOTHER_STATIC_ATOM: AtomicUsize = AtomicUsize::new(15); 35 | ANOTHER_STATIC_ATOM.store(9, SeqCst); 36 | assert_eq!(ANOTHER_STATIC_ATOM.load(SeqCst), 9); 37 | ``` 38 | 39 | **【Lint 检测】** 40 | 41 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 42 | | ------ | ---- | --------- | ------ | ------ | 43 | | [borrow_interior_mutable_const](https://rust-lang.github.io/rust-clippy/master/#borrow_interior_mutable_const) | yes| no | Style | warn | 44 | | [declare_interior_mutable_const](https://rust-lang.github.io/rust-clippy/master/#declare_interior_mutable_const) | yes| no | Style | warn | 45 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts/G.CNS.04.md: -------------------------------------------------------------------------------- 1 | ## G.CNS.04 不应在常量定义中增加显式的 `'static` 生命周期 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 在常量和静态变量声明时已经默认含有隐式的`'static`生命周期,所以不需要额外增加显式`'static`。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = 14 | &[...] 15 | static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] = 16 | &[...] 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | // 符合 23 | const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] 24 | static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...] 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 30 | | -------------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | ----- | 31 | | [redundant_static_lifetimes](https://rust-lang.github.io/rust-clippy/master/#redundant_static_lifetimes) | yes | no | Style | warn | 32 | 33 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/consts/G.CNS.05.md: -------------------------------------------------------------------------------- 1 | ## G.CNS.05 对于适用 `const fn` 的函数或方法宜尽可能地使用 `const fn` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 函数或方法缺失`const`关键词时无法被指派给常量。 8 | 9 | 但是要注意不是所有函数都能使用`const fn`,因为相比一般函数或方法,`const fn`在使用时会有限制,必须满足const 安全,如果不满足,编译器会报告错误信息。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | fn foo() -> usize { 15 | 10 16 | } 17 | 18 | // 不符合:必须是 constant 函数才能用于声明 const 常量 19 | const BAZ: usize = foo(); 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | const fn foo() -> usize { 26 | 10 27 | } 28 | 29 | const BAZ: usize = foo(); // 符合 30 | ``` 31 | 32 | **【例外】** 33 | 34 | ```rust 35 | const fn foo() -> bool { 36 | for _i in 0..5 {} // ERROR, 因为for loop默认不能用在const fn内(需要注明#![feature(const_for)]) 37 | false 38 | } 39 | ``` 40 | 41 | **【Lint 检测】** 42 | 43 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 44 | | ------ | ---- | --------- | ------ | ------ | 45 | | [missing_const_for_fn](https://rust-lang.github.io/rust-clippy/master/#missing_const_for_fn) | yes| no | Perf | warn | 46 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow.md: -------------------------------------------------------------------------------- 1 | # 3.6 控制流程 2 | 3 | Rust中流程控制也是属于表达式,但在本规范中将其独立出来。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow/G.CTF.01.md: -------------------------------------------------------------------------------- 1 | ## G.CTF.01 当需要通过多个`if`比较大小来区分不同情况时,优先使用`match`和`cmp`来代替`if`表达式 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在使用多个`if-else`来对不同情况进行区分时,使用 `match` 和 `cmp` 代替 `if` 的好处是语义更加明确,而且也能帮助开发者穷尽所有可能性。 8 | 但是这里需要注意这里使用 `match` 和 `cmp` 的性能要低于 `if`表达式,因为 一般的 `>` 或 `<` 等比较操作是内联的,而 `cmp`方法没有内联。 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | fn a() {} 14 | fn b() {} 15 | fn c() {} 16 | fn f(x: u8, y: u8) { 17 | // 不符合 18 | if x > y { 19 | a() 20 | } else if x < y { 21 | b() 22 | } else { 23 | c() 24 | } 25 | } 26 | ``` 27 | 28 | **【正例】** 29 | 30 | ```rust 31 | use std::cmp::Ordering; 32 | fn a() {} 33 | fn b() {} 34 | fn c() {} 35 | fn f(x: u8, y: u8) { 36 | // 符合 37 | match x.cmp(&y) { 38 | Ordering::Greater => a(), 39 | Ordering::Less => b(), 40 | Ordering::Equal => c() 41 | } 42 | } 43 | ``` 44 | 45 | **【Lint 检测】** 46 | 47 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 48 | | ------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 49 | | [comparison_chain](https://rust-lang.github.io/rust-clippy/master/#comparison_chain) | yes | no | style | warn | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow/G.CTF.02.md: -------------------------------------------------------------------------------- 1 | ## G.CTF.02 `if`条件表达式分支中如果包含了`else if`分支也应该包含`else`分支 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 这样做有助于代码逻辑更加健壮清晰,在一些要求严格的编码规范中要求这么做,比如《MISRA-C:2004 Rule 14.10》编码规范。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #[warn(clippy::else_if_without_else)] 13 | fn a() {} 14 | fn b() {} 15 | 16 | fn main(){ 17 | let x: i32 = 1; 18 | if x.is_positive() { 19 | a(); 20 | } else if x.is_negative() { 21 | b(); 22 | } // 不符合:没有 else 分支 23 | } 24 | ``` 25 | 26 | **【正例】** 27 | 28 | ```rust 29 | #[warn(clippy::else_if_without_else)] 30 | fn a() {} 31 | fn b() {} 32 | 33 | fn main(){ 34 | let x: i32 = 1; 35 | if x.is_positive() { 36 | a(); 37 | } else if x.is_negative() { 38 | b(); 39 | } else { 40 | // 符合 41 | } 42 | } 43 | ``` 44 | 45 | **【Lint 检测】** 46 | 47 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 48 | | -------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | ----- | 49 | | [else_if_without_else](https://rust-lang.github.io/rust-clippy/master/#else_if_without_else) | yes | no | restriction | allow | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow/G.CTF.03.md: -------------------------------------------------------------------------------- 1 | ## G.CTF.03 如果要通过 `if` 条件表达式来判断是否 Panic,请优先使用断言 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 略 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let sad_people: Vec<&str> = vec![]; 13 | // 不符合 14 | if !sad_people.is_empty() { 15 | panic!("there are sad people: {:?}", sad_people); 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | let sad_people: Vec<&str> = vec![]; 23 | // 符合 24 | assert!(sad_people.is_empty(), "there are sad people: {:?}", sad_people); 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 30 | | ------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 31 | | [if_then_panic](https://rust-lang.github.io/rust-clippy/master/#if_then_panic) | yes | no | Style | warn | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow/G.CTF.04.md: -------------------------------------------------------------------------------- 1 | ## G.CTF.04 在 Match 分支的 Guard 语句中不要使用带有副作用的条件表达式 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 因为在 mactch 分支中, 匹配几次就会执行 Guard 几次。如果携带副作用,会产生意料之外的情况。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合:下面代码会输出两次 "ha" 13 | fn main() { 14 | use std::cell::Cell; 15 | let i: Cell = Cell::new(0); 16 | match 1 { 17 | 1 | _ // 这里匹配两次 18 | if { // 这个 Guard 条件表达式带有副作用:打印,因为匹配两次,所以会执行两次 19 | println!("ha"); 20 | i.set(i.get() + 1); 21 | false 22 | } => {} 23 | _ => {} 24 | } 25 | assert_eq!(i.get(), 2); 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/control-flow/P.CTF.02.md: -------------------------------------------------------------------------------- 1 | ## P.CTF.02 优先使用模式匹配而非判断后再取值 2 | 3 | **【描述】** 4 | 5 | Rust 中 模式匹配 是惯用法,而不是通过 `if` 判断值是否相等。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | let opt: Option<_> = ...; 11 | // 不符合 12 | if opt.is_some() { 13 | let value = opt.unwrap(); 14 | ... 15 | } 16 | 17 | // 不符合 18 | let list: &[f32] = ...; 19 | 20 | if !list.is_empty() { 21 | let first = list[0]; 22 | ... 23 | } 24 | 25 | ``` 26 | 27 | **【正例】** 28 | 29 | ```rust 30 | // 符合 31 | if let Some(value) = opt { 32 | ... 33 | } 34 | // 符合 35 | if let [first, ..] = list { 36 | ... 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type.md: -------------------------------------------------------------------------------- 1 | # 3.4 数据类型 2 | 3 | 数据类型指 Rust 标准库提供的 原生类型,以及结构体和枚举体等编码实践。 4 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/G.TYP.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.02 数字字面量在使用的时候应该明确标注类型 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 如果数字字面量没有被指定具体类型,那么单靠类型推导,整数类型会被默认绑定为 `i32` 类型,而浮点数则默认绑定为 `f64`类型。这可能导致某些运行时的意外。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::default_numeric_fallback)] 13 | // 不符合 14 | let i = 10; // i32 15 | let f = 1.23; // f64 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | #![warn(clippy::default_numeric_fallback)] 22 | 23 | // 符合 24 | let i = 10u32; 25 | let f = 1.23f32; 26 | ``` 27 | 28 | **【Lint 检测】** 29 | 30 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 31 | | ---------------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 32 | | [default_numeric_fallback](https://rust-lang.github.io/rust-clippy/master/#default_numeric_fallback) | yes | no | restriction | allow | 33 | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/G.TYP.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.03 不要用数字类型边界值判断能否安全转换,而应使用 `try_from` 方法 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在 Rust 中 `From` 代表不能失败的转换,而 `TryFrom` 则允许返回错误。 8 | 9 | 一般在数字类型转换的时候,不需要防御式地去判断数字大小边界,那样可读性比较差,应该使用 `try_from` 方法,在无法转换的时候处理错误即可。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | #![warn(clippy::checked_conversions)] 15 | 16 | // 不符合 17 | let foo: u32 = 5; 18 | let _ = foo <= i16::MAX as u32; // 等价于 let _ = foo <= (i32::MAX as u32); 19 | ``` 20 | 21 | **【正例】** 22 | 23 | ```rust 24 | #![warn(clippy::checked_conversions)] 25 | 26 | // 符合 27 | let foo: u32 = 5; 28 | let f = i16::try_from(foo).is_ok(); // 返回 false 29 | ``` 30 | 31 | **【Lint 检测】** 32 | 33 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 34 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 35 | | [checked_conversions](https://rust-lang.github.io/rust-clippy/master/#checked_conversions) | yes | no | pedantic | allow | 36 | 37 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/P.TYP.01.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.01 必要时,应使类型可以表达更明确的语义,而不是只是直接使用原生类型 2 | 3 | **【描述】** 4 | 5 | 在类型中表达语义,可以增加代码的可读性。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | fn main() { 11 | // 不符合 12 | let years = 1942; 13 | } 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | // 符合: 语义更明确 20 | struct Years(i64); 21 | 22 | fn main() { 23 | let years = Years(1942); 24 | let years_as_primitive_1: i64 = years.0; 25 | let Years(years_as_primitive_2) = years; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/array.md: -------------------------------------------------------------------------------- 1 | # 数组 2 | 3 | 这里指固定长度数组。注意,不同长度的数组,被视为不同的类型。比如 `[T;1]`和 `[T;3]` 是两种不同的类型。 4 | 5 | 从 Rust 1.51 版本开始,稳定了常量泛型(const generics)功能,形如 `[T;1]`和 `[T;3]` 这种不同的类型可以统一为 `[T; N]`。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/array/G.TYP.ARR.01.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ARR.01 创建大全局数组时宜使用静态变量而非常量 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 因为常量会内联,对于大的数组,通常情况下,会使用其引用,使用静态变量定义更好。 8 | 9 | 栈上的数组大小以不超过 512KiB 为宜。 10 | 11 | > 虽然常量本质上是会内联,但 Rust 支持复制消除(Copy Elision)优化(非强制),而且在不断改进完善中,对于这种大的数据应该会有相关优化。 12 | 13 | 14 | **【反例】** 15 | 16 | ```rust 17 | #![warn(clippy::large_stack_arrays)] 18 | 19 | // 不符合 20 | pub const A: [u32;1_000_000] = [0u32; 1_000_000]; 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | #![warn(clippy::large_stack_arrays)] 27 | 28 | // 符合 29 | pub static A: [u32;1_000_000] = [0u32; 1_000_000]; 30 | ``` 31 | 32 | **【Lint 检测】** 33 | 34 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 35 | | ---------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 36 | | [large_const_arrays](https://rust-lang.github.io/rust-clippy/master/#large_const_arrays) | yes | no | perf | warn | 37 | | [large_stack_arrays](https://rust-lang.github.io/rust-clippy/master/#large_stack_arrays) | yes | no | pedantic | allow | 38 | 39 | 注意: `large_stack_arrays` 会检查在栈上分配的大数组,但clippy默认是 allow,根据实际使用场景决定是否针对这种情况发出警告。 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/array/G.TYP.ARR.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ARR.02 使用数组索引时禁止越界访问 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 越界访问在运行时会 Panic! 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | let x = [1, 2, 3, 4]; 14 | x[9]; 15 | &x[2..9]; 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | // 符合 22 | let x = [1, 2, 3, 4]; 23 | x[0]; 24 | x[3]; 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 30 | | ------------------------------------------------------------------------------------------------ | ------------- | ------------ | ----------- | --------- | 31 | | [out_of_bounds_indexing](https://rust-lang.github.io/rust-clippy/master/#out_of_bounds_indexing) | yes | no | correctness | deny | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/array/G.TYP.ARR.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ARR.03 当数组元素为原生数据类型(Primitive),排序时优先选用非稳定排序 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 稳定排序会消耗更多的内存和 CPU 周期,相对而言,非稳定排序性能更佳。 8 | 9 | 当然,在必须要稳定排序的场合,不应该使用非稳定排序。 10 | 11 | 注: `Vec` 动态数组也适用此规则 12 | 13 | **【反例】** 14 | 15 | ```rust 16 | // 不符合 17 | let mut vec = vec![2, 1, 3]; 18 | vec.sort(); // stable sort 19 | ``` 20 | 21 | **【正例】** 22 | 23 | ```rust 24 | // 符合 25 | let mut vec = vec![2, 1, 3]; 26 | vec.sort_unstable(); // unstable sort 27 | ``` 28 | 29 | **【例外】** 30 | 31 | ```rust 32 | // https://docs.rs/crate/solana-runtime/1.7.11/source/src/accounts_db.rs#:~:text=clippy%3a%3astable_sort_primitive 33 | pub fn generate_index(&self, limit_load_slot_count_from_snapshot: Option) { 34 | let mut slots = self.storage.all_slots(); 35 | #[allow(clippy::stable_sort_primitive)] 36 | slots.sort(); // 商业需求这里需要稳定排序 37 | // ... 38 | } 39 | ``` 40 | 41 | **【Lint 检测】** 42 | 43 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 44 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 45 | | [stable_sort_primitive](https://rust-lang.github.io/rust-clippy/master/#stable_sort_primitive) | yes | no | perf | warn | 46 | 47 | 当确实需要稳定排序时,需要修改该 lint 的设置为 `allow`。 48 | 49 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool.md: -------------------------------------------------------------------------------- 1 | # 布尔 2 | 3 | Rust 中的布尔类型有 `true`和`false`两种值。 4 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.02 如果 match 匹配表达式为布尔类型,宜使用 `if` 表达式来代替 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 对于布尔表达式更倾向于使用 `if ... else ...`,相比 `match` 模式匹配更有利于代码可读性。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::match_bool)] 13 | 14 | # fn foo() {} 15 | # fn bar() {} 16 | let condition: bool = true; 17 | // 不符合 18 | match condition { 19 | true => foo(), 20 | false => bar(), 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | #![warn(clippy::match_bool)] 28 | 29 | # fn foo() {} 30 | # fn bar() {} 31 | let condition: bool = true; 32 | // 符合 33 | if condition { 34 | foo(); 35 | } else { 36 | bar(); 37 | } 38 | ``` 39 | 40 | **【Lint 检测】** 41 | 42 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 43 | | ------------------------------------------------------------------------ | ------------- | ------------ | ----------- | --------- | 44 | | [logic_bug](https://rust-lang.github.io/rust-clippy/master/#logic_bug) | yes | no | correctness | deny | 45 | | [match_bool](https://rust-lang.github.io/rust-clippy/master/#match_bool) | yes | no | pedantic | allow | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.03 不应将数字类型转换为布尔值 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 这可能会让布尔值在内存中的表示无效。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let x = 1_u8; 13 | unsafe { 14 | // 不符合 15 | let _: bool = std::mem::transmute(x); // where x: u8 16 | } 17 | 18 | ``` 19 | 20 | **【Lint 检测】** 21 | 22 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 23 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 24 | | [transmute_int_to_bool](https://rust-lang.github.io/rust-clippy/master/#transmute_int_to_bool) | yes | no | complexity | warn | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.04.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.04 禁止在if表达式条件中使用块结构 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 为了增加可读性。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | if { true } { /* ... */ } 14 | 15 | # fn somefunc() -> bool { true }; 16 | // 不符合 17 | if { let x = somefunc(); x } { /* ... */ } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | // 符合 24 | if true { /* ... */ } 25 | 26 | # fn somefunc() -> bool { true }; 27 | let res = { let x = somefunc(); x }; 28 | // 符合 29 | if res { /* ... */ } 30 | ``` 31 | 32 | **【Lint 检测】** 33 | 34 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 35 | | -------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 36 | | [blocks_in_if_conditions](https://rust-lang.github.io/rust-clippy/master/#blocks_in_if_conditions) | yes | no | style | warn | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.05.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.05 非必要时,布尔运算应使用逻辑运算符( `&&/||`)而非位运算符 (`&/|`) 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 位运算不支持短路(short-circuiting),所以会影响性能。逻辑运算符则支持短路。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::needless_bitwise_bool)] 13 | let (x,y) = (true, false); 14 | if x & !y {} // 不符合:位运算符,不支持短路 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | #![warn(clippy::needless_bitwise_bool)] 21 | 22 | let (x,y) = (true, false); 23 | if x && !y {} // 符合:逻辑运算符,支持短路 24 | ``` 25 | 26 | **【Lint 检测】** 27 | 28 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 29 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 30 | | [needless_bitwise_bool](https://rust-lang.github.io/rust-clippy/master/#needless_bitwise_bool) | yes | no | pedantic | allow | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.06.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.06 不应使用数字代替布尔值 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | Rust 中布尔值就是 `true` 和 `false`。 不要试图使用数字 `1` 和 `0` 来代替布尔值。 8 | 9 | 虽然 布尔值 可以强转为 对应的数字,但是反之则不行。 10 | 11 | 不要通过判断数字来代替 布尔值,除非是 FFi 场景通过 C-ABI 和其他语言打交道。 12 | 13 | **【反例】** 14 | 15 | ```rust 16 | // 不符合 17 | let a = 1; 18 | let b = 0; 19 | assert_eq!(true, a == 1); 20 | assert_eq!(false, b == 0); 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | // 符合 27 | let a = true; 28 | let b = false; 29 | assert_eq!(true, a ); 30 | assert_eq!(false, b); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/bool/G.TYP.BOL.07.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.BOL.07 使用 `.not()` 方法代替逻辑取反运算符 (`!`) 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 逻辑取反运算符 (`!`) 是前缀一元运算符,相对较长的逻辑表达式来说很不显眼。 8 | 9 | 理解业务逻辑时,容易忽略取反符号,并且需要回头看。 10 | 11 | 使用 `.not()` 后缀方法 (`std::ops::Not`) 可以吸引注意力,视觉上更为连续。 12 | 13 | **【反例】** 14 | 15 | ```rust 16 | assert!(!self.map.contains(&key)); 17 | 18 | if !cache.contains(&key) { 19 | // ... 20 | } 21 | 22 | // 不符合:容易忽略取反符号 23 | ``` 24 | 25 | **【正例】** 26 | 27 | ```rust 28 | use std::ops::Not; 29 | 30 | assert!(self.map.contains(&key).not()); 31 | 32 | if cache.contains(&key).not() { 33 | // ... 34 | } 35 | 36 | // 符合:`.not()` 更容易吸引注意力 37 | ``` 38 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/char.md: -------------------------------------------------------------------------------- 1 | # 字符 2 | 3 | 在 Rust 中,字符是一个合法的 Unicode 标量值(Unicode scalar value),一个字符大小为 4 字节,对应一个 Unicode 码位(CodePoint)。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/char/G.TYP.CHR.01.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.CHR.01 不应将字符字面量强制转换为 `u8` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 应该使用字节字面量,而不应使用字符字面量强转为 `u8`。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | 'x' as u8 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | // 符合 20 | b'x' 21 | ``` 22 | 23 | **【Lint 检测】** 24 | 25 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 26 | | -------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 27 | | [char_lit_as_u8](https://rust-lang.github.io/rust-clippy/master/#char_lit_as_u8) | yes | no | complexity | warn | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/char/G.TYP.CHR.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.CHR.02 字符串方法中如果需要单个字符的值作为参数,宜使用字符而非字符串 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 大部分情况下,使用字符比用字符串性能更好。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | let s = "yxz"; 14 | s.split("x"); 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | // 符合 21 | let s = "yxz"; 22 | s.split('x'); 23 | ``` 24 | 25 | 26 | **【Lint 检测】** 27 | 28 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 29 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 30 | | [single_char_pattern](https://rust-lang.github.io/rust-clippy/master/#single_char_pattern) | yes | no | perf | warn | 31 | 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/char/G.TYP.CHR.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.CHR.03 需要将整数转换为字符时,应使用安全转换函数,而非 `transmute` 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 并非每个整数都对应一个合法的 Unicode 标量值,使用 `transmute` 转换会有未定义行为。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let x = 37_u32; 13 | unsafe { 14 | // 不符合 15 | let x: char = std::mem::transmute(x); // where x: u32 16 | assert_eq!('%', x); 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | let x = 37_u32; 24 | 25 | // 符合:x 会返回一个 Result 类型,开发者可以进行错误处理 26 | let x = std::char::from_u32(x); 27 | assert_eq!('%', x); 28 | 29 | // 符合:如果确定该整数对应合法的 unicode,可以使用 uncheck 方法加速 30 | let x = unsafe {std::char::from_u32_unchecked(x) }; 31 | assert_eq!('%', x); 32 | ``` 33 | 34 | **【Lint 检测】** 35 | 36 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 37 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 38 | | [transmute_int_to_char](https://rust-lang.github.io/rust-clippy/master/#transmute_int_to_char) | yes | no | complexity | warn | 39 | 40 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/enum.md: -------------------------------------------------------------------------------- 1 | # 枚举体 2 | 3 | Rust 的枚举是一种带 Tag 的联合体。 一般分为三类:空枚举、无字段(fieldless)枚举和数据承载(data carrying)枚举。 4 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/enum/G.TYP.ENM.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ENM.03 在使用类似 C 语言的枚举写法且使用`repr(isize/usize)` 布局时注意 32位架构上截断的问题 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在使用类似 C 语言的枚举写法且使用`repr(isize/usize)` 布局时,在32位架构上会截断变体值,但在64位上工作正常。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | 13 | #[repr(usize)] 14 | enum NonPortable { 15 | X = 0x1_0000_0000, // 不符合:如果在 32位架构上会截断变体值,导致该指针地址变化 16 | Y = 0, 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | 因为当前 lint 默认是 `deny`,所以需要将其配置为 `allow`。 23 | 24 | ```rust 25 | #![allow(clippy::enum_clike_unportable_variant)] 26 | 27 | #[repr(isize)] 28 | pub enum ZBarColor { 29 | ZBarSpace = 0, // 符合:因为值足够小,没有截断风险 30 | ZBarBar = 1, 31 | } 32 | 33 | // 符合:没有指定 repr(isize/usize) 34 | #[allow(clippy::enum_clike_unportable_variant)] 35 | pub(crate) enum PropertyType { 36 | ActionItemSchemaVersion = 0x0C003473, 37 | ActionItemStatus = 0x10003470, 38 | ActionItemType = 0x10003463, 39 | Author = 0x1C001D75, 40 | } 41 | ``` 42 | 43 | **【Lint 检测】** 44 | 45 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 46 | | -------------------------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 47 | | [enum_clike_unportable_variant](https://rust-lang.github.io/rust-clippy/master/#enum_clike_unportable_variant) | yes | no | correctness | deny | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/enum/G.TYP.ENM.05.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ENM.05 对外导出的公开Enum,宜添加`#[non_exhaustive]`属性 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 作为对外公开的 Enum,为了保持稳定性,应该使用 `#[non_exhaustive]`属性,避免因为将来Enum 枚举变体的变化而影响到下游的使用。 8 | 9 | 10 | **【反例】** 11 | 12 | 在 `#[non_exhaustive]` 属性稳定之前,社区内还有一种约定俗成的写法来达到防止下游自定义枚举方法。 13 | 14 | ```rust 15 | #![warn(clippy::exhaustive_enums)] 16 | 17 | enum E { 18 | A, 19 | B, 20 | #[doc(hidden)] 21 | _C, // 不符合: 这里用 下划线作为前缀定义的变体,作为隐藏的变体,不对外展示 22 | } 23 | ``` 24 | 25 | **【正例】** 26 | 27 | ```rust 28 | // 符合 29 | #[non_exhaustive] 30 | enum E { 31 | A, 32 | B, 33 | } 34 | ``` 35 | 36 | **【Lint 检测】** 37 | 38 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 39 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 40 | | [exhaustive_enums](https://rust-lang.github.io/rust-clippy/master/#exhaustive_enums) | yes | no | restriction | allow | 41 | | [manual_non_exhaustive](https://rust-lang.github.io/rust-clippy/master/#manual_non_exhaustive) | yes | no | style | warn | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/enum/G.TYP.ENM.06.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ENM.06 Enum内变体的大小差异不宜过大 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 要注意 Enum 内变体的大小差异不要过大,因为 Enum 内存布局是以最大的变体进行对齐。根据场景,如果该Enum 实例中小尺寸变体的实例使用很多的话,内存就会有所浪费。如果小尺寸变体的实例使用很少,则影响不大。 8 | 9 | 解决办法之一为把大尺寸变体包含到 `Box`中。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | // 不符合 15 | enum Test { 16 | A(i32), 17 | B([i32; 1000]), 18 | C([i32; 8000]), 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | // 符合 26 | enum Test { 27 | A(i32), 28 | B(Box<[i32; 1000]>), 29 | C(Box<[i32; 8000]>), 30 | } 31 | ``` 32 | 33 | **【Lint 检测】** 34 | 35 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 36 | | ---------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 37 | | [large_enum_variant](https://rust-lang.github.io/rust-clippy/master/#large_enum_variant) | yes | no | perf | warn | 38 | 39 | 该 lint 可以通过 clippy 配置项 `enum-variant-size-threshold = 200` 来配置,默认是 `200` 字节。 40 | 41 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/enum/G.TYP.ENM.07.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.ENM.07 如需依赖 Enum 中变体的序数,则应为变体设置明确的数值 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 在日常开发中,有时需要产生出有独立名称,且为连续或是有规律的数值,用来当作接口的参数数值,一般采用枚举(Enum)来实现。 8 | 9 | Rust 语言的枚举变体的序数(ordinal)依赖于它的定义顺序。在开发过程中,很有可能需要新增变体。一般情况下,都是从尾部追加变体,但不排除有人会从中间新增变体,或者,依赖于某些库将变体自动按字典序排序,这样就有可能打乱枚举变体本来到顺序,导致程序中依赖变体序数的代码产生逻辑错误。 10 | 11 | 所以,在这种情况下,我们需要为变体设置明确的数值。 12 | 13 | **【反例】** 14 | 15 | ```rust 16 | // 不符合 17 | enum Mode { 18 | Mode0, // 0 19 | Mode1, // 1 20 | Mode3, // 2 21 | Mode2, // 3 22 | } 23 | 24 | fn main() { 25 | // 不符合:报错,此处 Mode::Mode3 对应值为 2 ,而不是 3 26 | assert_eq!(3, Mode::Mode3 as u8); 27 | } 28 | ``` 29 | 30 | **【正例】** 31 | 32 | ```rust 33 | // 符合 34 | enum Mode { 35 | Mode0 = 0, 36 | Mode1 = 1, 37 | Mode3 = 3, 38 | Mode2 = 2, 39 | } 40 | 41 | fn main() { 42 | // 符合 43 | assert_eq!(3, Mode::Mode3 as u8); 44 | } 45 | 46 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/float.md: -------------------------------------------------------------------------------- 1 | # 浮点数 2 | 3 | Rust 的浮点数包括 `f32` 和 `f64` 两种类型。Rust 编译器默认推断的 Float 类型是 `f64`。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/float/G.TYP.FLT.01.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.FLT.01 使用浮点数字面量时,要警惕是否存在被Rust编译器截断的风险 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 当指定超过类型精度(`f32` 或 `f64`)的字面量值时,Rust 会默认截断该值。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | let v: f32 = 0.123_456_789_9; 14 | println!("{}", v); // 0.123_456_789 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | // 符合 21 | let v: f64 = 0.123_456_789_9; 22 | println!("{}", v); // 0.123_456_789_9 23 | ``` 24 | 25 | **【Lint 检测】** 26 | 27 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 28 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 29 | | [excessive_precision](https://rust-lang.github.io/rust-clippy/master/#excessive_precision) | yes | no | style | warn | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/float/G.TYP.FLT.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.FLT.02 从任何数字类型转换为浮点类型时注意避免损失精度 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 开发者了解发生精度损失的位置,会对解决因为转换而损失精度的问题更加有好处。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::cast_precision_loss)] 13 | 14 | fn main(){ 15 | // 不符合 16 | let x = u64::MAX; 17 | x as f64; 18 | // 不符合 19 | let x: f32 = 16_777_219.0 ; // 该数字转换为 f64 后会表示为 16_777_220.0 20 | x as f64; 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | #![warn(clippy::cast_precision_loss)] 28 | 29 | fn main(){ 30 | // 符合 31 | let x = i32::MAX; 32 | let y = f64::from(x); // 如果 x 为 u64 类型,则编译会出错,不接受这类转换 33 | // 符合 34 | let x: f32 = 16_777_219.0 ; 35 | let y = f64::from(x); // 该数字转换为 f32 后会表示为 16_777_220.0 36 | println!("{y:?}") 37 | } 38 | ``` 39 | 40 | **【Lint 检测】** 41 | 42 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 43 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 44 | | [cast_precision_loss](https://rust-lang.github.io/rust-clippy/master/#cast_precision_loss) | yes | no | pedantic | allow | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/float/G.TYP.FLT.05.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.FLT.05 禁止在浮点数和整数相互转换时使用 `transmute` 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 使用 `transmute` 转换容易产生未定义行为,建议使用 `to_bites` 这样转换更加安全。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | unsafe { 14 | let _: u32 = std::mem::transmute(1f32); 15 | let _: f32 = std::mem::transmute(1_u32); 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | //符合 23 | let _: u32 = 1f32.to_bits(); 24 | let _: f32 = f32::from_bits(1_u32); 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 30 | | ------------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 31 | | [transmute_float_to_int](https://rust-lang.github.io/rust-clippy/master/#transmute_float_to_int) | yes | no | complexity | warn | 32 | | [transmute_int_to_float](https://rust-lang.github.io/rust-clippy/master/#transmute_int_to_float) | yes | no | complexity | warn | 33 | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/int.md: -------------------------------------------------------------------------------- 1 | # 整数 2 | 3 | Rust 中有目前有十二种整数类型:`i8/u8`, `i16/u16`, `i32/u32`, `i64/u64`, `i128/u128`, `isize/usize` 。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/int/G.TYP.INT.03.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.INT.03 对负数取模计算的时候不应使用`%` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 中的 `%` 符号为余数运算符,它的行为与`C`或`Java`等语言中相同符号的运算符相同。它也类似于`Python`或`Haskell`等语言中的模(modulo)运算符,只是它对负数的行为不同:余数是基于截断除法,而模运算是基于向下取整(floor)除法。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::modulo_arithmetic)] 13 | 14 | fn main() { 15 | let a: i32 = -1; 16 | let b: i32 = 6; 17 | // 余数运算符只是返回第一个操作数除以第二个操作数的余数。所以 -1/6 给出 0,余数为 -1 18 | assert_eq!(a % b, -1); 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | #![warn(clippy::modulo_arithmetic)] 26 | 27 | fn main() { 28 | let a: i32 = -1; 29 | let b: i32 = 6; 30 | // 取模是严格低于第二个操作数的自然数(所以是非负数),与第二个操作数的最大倍数相加,也低于或等于第一个操作数,则为第一个操作数。 31 | // 6的最大倍数低于或等于-1 是 -6(6*-1),模数是5,因为-6+5=-1。 32 | assert_eq!(a.rem_euclid(b), 5); 33 | } 34 | ``` 35 | 36 | **【Lint 检测】** 37 | 38 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 39 | | -------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 40 | | [modulo_arithmetic](https://rust-lang.github.io/rust-clippy/master/#modulo_arithmetic) | yes | no | restriction | allow | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/ref.md: -------------------------------------------------------------------------------- 1 | # 引用 2 | 3 | 在 Rust 中,引用是有借用检查的指针,就像穿着“安全的外衣”。没有借用检查的指针也叫裸指针。 4 | 5 | Rust 编译器总是希望引用是非空且对齐的。 6 | 7 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/ref/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rust-Coding-Guidelines/rust-coding-guidelines-zh/6b3fc48b285b4f87696634a3e18572d010b30fd4/src/safe-guides/coding_practice/data-type/ref/.keep -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/slice.md: -------------------------------------------------------------------------------- 1 | # 切片 2 | 3 | 切片(slice)允许开发者引用集合中连续的元素序列,类型签名用 `[T]`表示,但因为它是动态大小类型(DST),所以一般用 `&[T]` 表示切片。 4 | 5 | `&str` 就是一种字符串切片。 6 | 7 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/slice/P.TYP.SLC.01.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.SLC.01 宜使用切片迭代器来代替手工索引 2 | 3 | **【描述】** 4 | 5 | 在 for 循环中使用索引是比较常见的编程习惯,但是这种方式是最有可能导致边界错误的。 6 | 7 | 利用 切片自带的方法,并利用迭代器,可以避免这种错误。 8 | 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | let points: Vec = ...; 14 | let differences = Vec::new(); 15 | 16 | // 不符合:人工计算长度选择范围很可能会出错 17 | for i in 1..points.len() [ 18 | let current = points[i]; 19 | let previous = points[i-1]; 20 | differences.push(current - previous); 21 | ] 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | let points: Vec = ...; 28 | let mut differences = Vec::new(); 29 | 30 | // 符合:切片提供 windows 或 array_windows 方法返回迭代器 31 | for [previous, current] in points.array_windows().copied() { 32 | differences.push(current - previous); 33 | } 34 | ``` 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/slice/P.TYP.SLC.02.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.SLC.02 宜使用切片模式来提升代码的可读性 2 | 3 | **【描述】** 4 | 5 | 切片也支持模式匹配,适当应用切片模式,可以有效提升代码可读性。 6 | 7 | **【正例】** 8 | 9 | 利用切片模式编写判断回文字符串(如"aba"、"abba"之类)的函数。代码来自于:[Daily Rust: Slice Patterns](https://adventures.michaelfbryan.com/posts/daily/slice-patterns/#matching-the-start-of-a-slice),还有更多用例。 10 | 11 | ```rust 12 | pub fn word_is_palindrome(word: &str) -> bool { 13 | let letters: Vec<_> = word.chars().collect(); 14 | 15 | is_palindrome(&letters) 16 | } 17 | // 符合:利用切片模式匹配来判断是否回文字符串 18 | fn is_palindrome(items: &[char]) -> bool { 19 | match items { 20 | [first, middle @ .., last] => first == last && is_palindrome(middle), 21 | [] | [_] => true, 22 | } 23 | } 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/struct.md: -------------------------------------------------------------------------------- 1 | # 结构体 2 | 3 | Rust 包含了三种结构体: 命名结构体、元组结构体、单元结构体。 4 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/struct/G.TYP.SCT.02.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.SCT.02 当结构体中有超过三个布尔类型的字段,宜将其独立为新的枚举类 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 这样有助于提升 代码可读性和 API 。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::struct_excessive_bools)] 13 | 14 | // 不符合 15 | struct S { 16 | name: String, 17 | is_pending: bool, 18 | is_processing: bool, 19 | is_finished: bool, 20 | } 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | #![warn(clippy::struct_excessive_bools)] 27 | // 符合 28 | struct S { 29 | name: String, 30 | state: State, 31 | } 32 | 33 | enum State { 34 | Pending, 35 | Processing, 36 | Finished, 37 | } 38 | ``` 39 | 40 | **【Lint 检测】** 41 | 42 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 43 | | ------------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 44 | | [struct_excessive_bools](https://rust-lang.github.io/rust-clippy/master/#struct_excessive_bools) | yes | no | pedantic | allow | 45 | 46 | 该 lint 对应 `clippy.toml` 配置项: 47 | 48 | ```toml 49 | # 用于配置结构体可以拥有的 bool 类型字段最大数量,默认为 3。 50 | max-struct-bools = 3 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/struct/P.TYP.SCT.01.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.SCT.01 为结构体实现构造性方法时,避免构造后再初始化的情况 2 | 3 | **【描述】** 4 | 5 | 跟其他OOP 或 FP 语言不一样, Rust 的惯用方式是构建即初始化。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 不符合 11 | // 先构建 12 | let mut dict = Dictionary::new(); 13 | // 后初始化 14 | dict.load_from_file("./words.txt")?; 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | // 符合 21 | // 构建即初始化 22 | let dict = Dictionary::from_file("./words.txt")?; 23 | 24 | impl Dictionary { 25 | fn from_file(filename: impl AsRef) -> Result { 26 | let text = std::fs::read_to_string(filename)?; 27 | // 不会去存储空状态 28 | let mut words = Vec::new(); 29 | for line in text.lines() { 30 | words.push(line); 31 | } 32 | Ok(Dictionary { words }) 33 | } 34 | } 35 | ``` 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/struct/P.TYP.SCT.02.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.SCT.02 结构体实例需要默认实现时,宜使用`Default`特质 2 | 3 | **【描述】** 4 | 5 | 为结构体实现 `Default` 对于简化代码提高可读性很有帮助。 6 | 7 | **【示例】** 8 | 9 | ```rust 10 | use std::{path::PathBuf, time::Duration}; 11 | 12 | #[derive(Default, Debug, PartialEq)] 13 | struct MyConfiguration { 14 | output: Option, 15 | search_path: Vec, 16 | timeout: Duration, 17 | check: bool, 18 | } 19 | 20 | fn main() { 21 | // 使用 default 方法创建实例 22 | let mut conf = MyConfiguration::default(); 23 | conf.check = true; 24 | println!("conf = {:#?}", conf); 25 | 26 | // 创建新实例的时候,使用局部更新更加方便 27 | let conf1 = MyConfiguration { 28 | check: true, 29 | ..Default::default() 30 | }; 31 | assert_eq!(conf, conf1); 32 | } 33 | ``` 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/tuple.md: -------------------------------------------------------------------------------- 1 | # 元组 2 | 3 | 元组是异构复合类型,可以存储多个不同类型的元素。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/tuple/G.TYP.TUP.01.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.TUP.01 使用元组时,其元素不宜超过3个 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 元组是异构复合类型,元素过多,其表达力会下降,影响代码可读性和可维护性。 8 | 9 | 尤其是利用元组作为函数返回值时,不宜过多。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | // 不符合:超过3个元组参数 15 | fn convert(x: i8) -> (i8, i16, i32, i64, f32, f64) { 16 | (x as i8, x as i16, x as i32, 17 | x as i64, x as f32, x as f64) 18 | } 19 | 20 | fn main(){ 21 | let _ = convert(3); 22 | } 23 | ``` 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/unit.md: -------------------------------------------------------------------------------- 1 | # 单元类型 2 | 3 | Rust 中单元类型为零大小类型。其类型签名和值都为 `()`,它也是一个空元组。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/unit/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rust-Coding-Guidelines/rust-coding-guidelines-zh/6b3fc48b285b4f87696634a3e18572d010b30fd4/src/safe-guides/coding_practice/data-type/unit/.keep -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/vec.md: -------------------------------------------------------------------------------- 1 | # 动态数组 2 | 3 | 这里指可以动态增长的数组`Vec`。 4 | 5 | 在数组一节中有[部分原则和规则](./array.md)也适用于这里。 6 | 7 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/vec/G.TYP.VEC.01.md: -------------------------------------------------------------------------------- 1 | ## G.TYP.VEC.01 禁止访问未初始化的数组 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 访问未初始化数组的内存会导致未定义行为。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let mut vec: Vec = Vec::with_capacity(1000); 13 | unsafe { vec.set_len(1000); } 14 | // 不符合 15 | reader.read(&mut vec); // error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | // 符合 22 | let mut vec: Vec = vec![0; 1000]; 23 | reader.read(&mut vec); 24 | ``` 25 | 26 | **【Lint 检测】** 27 | 28 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 29 | | ------------------------------------------------------------ | ------------- | ------------ | ----------- | ----- | 30 | | [uninit_vec](https://rust-lang.github.io/rust-clippy/master/#uninit_vec) | yes | no | correctness | deny | 31 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/vec/P.TYP.VEC.01.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.VEC.01 非必要时不宜使用动态数组 2 | 3 | **【描述】** 4 | 5 | 非必须不宜使用 `Vec`,应该优先尝试使用固定长度数组或常量泛型。 6 | 7 | 或者可以参考第三方库,诸如 [`smallvec`](https://docs.rs/smallvec/latest/smallvec) ,在元素比较少量的时候,可以放到栈上进行管理,如果超过一定元素才会选择堆内存。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | fn main() { 13 | // 不符合 14 | let v: Vec = vec![1, 2, 3]; 15 | println!("{:#}", v); 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | fn main() { 23 | // 符合 24 | let v = [1, 2, 3]; 25 | println!("{:#?}", v); 26 | } 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/data-type/vec/P.TYP.VEC.02.md: -------------------------------------------------------------------------------- 1 | ## P.TYP.VEC.02 创建动态数组时,宜预先分配足够容量,避免后续操作中产生多次分配 2 | 3 | **【描述】** 4 | 5 | 预分配足够的容量,避免后续内存分配,可以提升代码性能。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 不符合 11 | let mut output = Vec::new(); 12 | ``` 13 | 14 | **【正例】** 15 | 16 | ```rust 17 | // 符合 18 | let mut output = Vec::with_capacity(input.len()); 19 | ``` 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/error-handle.md: -------------------------------------------------------------------------------- 1 | # 3.12 错误处理 2 | 3 | Rust 为了保证系统健壮性,将系统中出现的非正常情况划分为三大类: 4 | 5 | 1. 失败 6 | 2. 错误 7 | 3. 异常 8 | 9 | Rust 语言针对这三类非正常情况分别提供了专门的处理方式,让开发者可以分情况去选择。 10 | 11 | - 对于失败的情况,可以使用断言工具。 12 | - 对于错误,Rust 提供了基于返回值的分层错误处理方式,比如 Option 可以用来处理可能存在空值的情况,而 Result 就专门用来处理可以被合理解决并需要传播的错误。 13 | - 对于异常,Rust 将其看作无法被合理解决的问题,提供了线程恐慌机制,在发生异常的时候,线程可以安全地退出。 14 | 15 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/error-handle/G.ERR.01.md: -------------------------------------------------------------------------------- 1 | ## G.ERR.01 在处理 `Option` 和 `Result` 类型时,不要随便使用 `unwrap` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 当 `Option` 和 `Result`类型的值分别是 `None` 或 `Err` 时,直接对其 `unwrap()` 会导致程序恐慌! 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::unwrap_used)] 13 | 14 | fn select(opt: Option) { 15 | opt.unwrap(); // 不符合 16 | } 17 | // OR 18 | fn select(opt: Result) { 19 | res.unwrap(); // 不符合 20 | } 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | #![warn(clippy::unwrap_used)] 27 | 28 | fn select(opt: Option) { 29 | opt.expect("more helpful message"); // 符合:可以用 expect 方法来处理 None 的情况 30 | } 31 | // OR 32 | fn select(opt: Result) { 33 | res.expect("more helpful message"); // 符合:可以用 expect 方法来处理 Err 的情况 34 | } 35 | ``` 36 | 37 | **【Lint 检测】** 38 | 39 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 40 | | -------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 41 | | [unwrap_used](https://rust-lang.github.io/rust-clippy/master/#unwrap_used) | yes | no | restriction | allow | 42 | 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/error-handle/P.ERR.01.md: -------------------------------------------------------------------------------- 1 | ## P.ERR.01 当传入函数的参数值因为超出某种限制可能会导致函数调用失败,应该使用断言 2 | 3 | **【描述】** 4 | 5 | 当传入函数的某个参数值可能因为超出某种限制,比如超出数组长度的索引、字符串是否包含某个字符、数组是否为空等,应该使用断言。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | // From: std::vec::Vec::swap_remove 11 | #[stable(feature = "rust1", since = "1.0.0")] 12 | pub fn swap_remove(&mut self, index: usize) -> T { 13 | #[cold] 14 | #[inline(never)] 15 | fn assert_failed(index: usize, len: usize) -> ! { 16 | panic!("swap_remove index (is {}) should be < len (is {})", index, len); 17 | } 18 | 19 | let len = self.len(); 20 | 21 | if index >= len { 22 | // 此处使用断言方法,虽然不是标准库内置断言宏,但也是一种断言 23 | assert_failed(index, len); 24 | } 25 | unsafe { 26 | let last = ptr::read(self.as_ptr().add(len - 1)); 27 | let hole = self.as_mut_ptr().add(index); 28 | self.set_len(len - 1); 29 | ptr::replace(hole, last) 30 | } 31 | } 32 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr.md: -------------------------------------------------------------------------------- 1 | # 3.5 表达式 2 | 3 | Rust 中几乎一切皆表达式。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.01.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.01 当需要对表达式求值后重新赋值时,宜使用复合赋值模式 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 略 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let mut a = 5; 13 | let b = 0; 14 | a = a + b; // 不符合 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | let mut a = 5; 21 | let b = 0; 22 | a += b; // 符合 23 | ``` 24 | 25 | **【Lint 检测】** 26 | 27 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 28 | | -------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 29 | | [assign_op_pattern](https://rust-lang.github.io/rust-clippy/master/#assign_op_pattern) | yes | no | style | warn | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.02.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.02 不宜在比较中使用不兼容的位掩码 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 如果比较的位总是被位掩码设置为零或一,则比较是常量true或 false(取决于掩码、比较值和运算符),这种代码是有误导性的,可能是故意这么写用于赢得一场性能竞赛或者是通过一个测试用例。 8 | 9 | 可以对照下面表格进行检查。 10 | 11 | | Comparison | Bit Op | Example | is always | Formula | 12 | | ---------- | ------ | --------------- | --------- | --------------- | 13 | | == or != | & | x & 2 == 3 | false | c & m != c | 14 | | < or >= | & | x & 2 < 3 | true | m < c | 15 | | > or <= | & | x & 1 > 1 | false | m <= c | 16 | | == or != | | | x | 1 == 0 | false | c | m != c | 17 | | < or >= | | | x | 1 < 1 | false | m >= c | 18 | | <= or > | | | x | 1 > 0 | true | m > c | 19 | 20 | **【反例】** 21 | 22 | ```rust 23 | let x = 2; 24 | // 不符合:该表达式会永远是 false 25 | if (x & 1 == 2) { } 26 | ``` 27 | 28 | **【正例】** 29 | 30 | ```rust 31 | let x = 2; 32 | // 符合 33 | if (x == 2) { } 34 | ``` 35 | 36 | **【Lint 检测】** 37 | 38 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 39 | | ---------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 40 | | [bad_bit_mask](https://rust-lang.github.io/rust-clippy/master/#bad_bit_mask) | yes | no | correctness | deny | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.03.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.03 不应利用数组表达式的边界检查来 Panic,而应使用断言 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 这样会影响代码可读性。使用断言可以更好的描述代码的意图。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | fn main(){ 13 | // 不符合 14 | [42, 55][get_usize()]; 15 | compute_array()[0]; 16 | } 17 | 18 | fn get_usize() -> usize { 19 | 6 20 | } 21 | 22 | fn compute_array() -> [i32; 3] { 23 | [1,2,3] 24 | } 25 | ``` 26 | 27 | **【正例】** 28 | 29 | ```rust 30 | fn main(){ 31 | // 符合 32 | assert!([42, 55].len() > get_usize()); 33 | assert!(compute_array().len() > 0); 34 | } 35 | 36 | fn get_usize() -> usize { 37 | 6 38 | } 39 | 40 | fn compute_array() -> [i32; 3] { 41 | [1,2,3] 42 | } 43 | ``` 44 | 45 | **【Lint 检测】** 46 | 47 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 48 | | ---------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 49 | | [unnecessary_operation](https://rust-lang.github.io/rust-clippy/master/#unnecessary_operation) | yes | no | complexity | warn | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.04.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.04 自增或自减运算使用`+=`或`-=` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | C/Cpp 等编程语言常用的自增自减操作,如 `++i` 、`i++` 、`i--` 等不是合法的 Rust 表达式, `--i` 虽然是合法的 Rust 表达式,但是是表达对i的符号取反两次,而不是自减语义。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let mut x = 3; 13 | --x; // 不符合:x 的值还是 3 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | let mut x = 3; 20 | x -= 1; // 符合 21 | ``` 22 | 23 | **【Lint 检测】** 24 | 25 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 26 | | ------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 27 | | [double_neg](https://rust-lang.github.io/rust-clippy/master/#double_neg) | yes | no | style | warn | 28 | 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.05.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.05 使用括号来清楚表示表达式的计算顺序 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 并不是每个人都能记得住优先级,所以最好使用括号把优先级顺序区分出来,增加可读性。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | 1 << 2 + 3 // 不符合 13 | -1i32.abs() // 不符合 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | (1 << 2) + 3 // 符合 20 | (-1i32).abs() // 符合 21 | ``` 22 | 23 | **【Lint 检测】** 24 | 25 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 26 | | ------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 27 | | [precedence](https://rust-lang.github.io/rust-clippy/master/#precedence) | yes | no | complexity | warn | 28 | 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/expr/G.EXP.06.md: -------------------------------------------------------------------------------- 1 | ## G.EXP.06 避免在比较中添加无用的掩码操作 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 检查比较中的无用位掩码操作,可以在不改变结果的情况下删除该位掩码操作。 8 | 9 | 请对照下面表格进行检查。 10 | 11 | | Comparison | Bit Op | Example | equals | 12 | | ---------- | ---------- | -------------- | ------ | 13 | | \> / <= | | / ^ | x | 2 > 3 | x > 3 | 14 | | < / >= | | / ^ | x ^ 1 < 4 | x < 4 | 15 | 16 | **【反例】** 17 | 18 | ```rust 19 | // 不符合 20 | if (x | 1 > 3) { } 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | // 符合 27 | if (x > 3) { } 28 | ``` 29 | 30 | **【Lint 检测】** 31 | 32 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 33 | | -------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 34 | | [ineffective_bit_mask](https://rust-lang.github.io/rust-clippy/master/#ineffective_bit_mask) | yes | no | correctness | deny | 35 | 36 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design.md: -------------------------------------------------------------------------------- 1 | # 3.9 函数设计 2 | 3 | 创建函数或使用闭包时需要注意的地方。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/G.FUD.01.md: -------------------------------------------------------------------------------- 1 | ## G.FUD.01 函数参数最长不要超过五个 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 为了提升代码可读性,函数的参数最长不宜超过五个。根据编译器函数调用规范[[1](https://www.cnblogs.com/shines77/p/3788514.html)][[2](https://learn.microsoft.com/zh-cn/cpp/build/x64-calling-convention?view=msvc-170)],较少的参数个数编译器优先使用寄存器,所以 8 | 存在性能收益的可能性。 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | struct Color; 14 | // 不符合 15 | fn foo(x: f32, y: f32, name: &str, c: Color, w: u32, h: u32, a: u32, b: u32) { 16 | // .. 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | 想办法把过长的参数缩短。 23 | 24 | ```rust 25 | struct Color; 26 | // 符合:此处使用 常量泛型(const generic) 来接收后面多个 u32 类型的参数 27 | // 使用元组 缩短 2~3 个参数为一个参数 28 | fn foo(x: (f32, f32), name: &str, c: Color, last: [T; N]) { 29 | ; 30 | } 31 | 32 | fn main(){ 33 | let arr = [1u32, 2u32]; 34 | foo((1.0f32, 2.0f32), "hello", Color, arr); 35 | let arr = [1.0f32, 2.0f32, 3.0f32]; 36 | foo((1.0f32, 2.0f32), "hello", Color, arr); 37 | } 38 | ``` 39 | 40 | **【Lint 检测】** 41 | 42 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 43 | | ---------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 44 | | [too_many_arguments](https://rust-lang.github.io/rust-clippy/master/#too_many_arguments) | yes | no | complexity | warn | 45 | 46 | 该 lint 对应 `clippy.toml` 配置项: 47 | 48 | ```toml 49 | # 函数参数最长不要超过5个 50 | too-many-arguments-threshold = 5 51 | ``` 52 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/G.FUD.02.md: -------------------------------------------------------------------------------- 1 | ## G.FUD.02 当函数参数实现了 Copy,并且是按值传入,如果值可能会太大,则宜考虑按引用传递 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 通过值传递的参数可能会导致不必要的 `memcpy` 拷贝,这可能会造成性能损失。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::large_types_passed_by_value)] 13 | 14 | #[derive(Clone, Copy)] 15 | struct TooLarge([u8; 2048]); 16 | 17 | // 不符合 18 | fn foo(v: TooLarge) {} 19 | ``` 20 | 21 | **【正例】** 22 | 23 | ```rust 24 | #![warn(clippy::large_types_passed_by_value)] 25 | 26 | #[derive(Clone, Copy)] 27 | struct TooLarge([u8; 2048]); 28 | 29 | // 符合 30 | fn foo(v: &TooLarge) {} 31 | ``` 32 | 33 | **【Lint 检测】** 34 | 35 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 36 | | ---------------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 37 | | [large_types_passed_by_value](https://rust-lang.github.io/rust-clippy/master/#large_types_passed_by_value) | yes | no | pedantic | allow | 38 | 39 | 该 lint 对应 `clippy.toml` 配置项: 40 | 41 | ```toml 42 | # 如果函数是被导出的 API,则该 lint 不会被触发,是防止 lint 建议对 API 有破坏性的改变。默认为 true 43 | avoid-breaking-exported-api=true 44 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/G.FUD.03.md: -------------------------------------------------------------------------------- 1 | ## G.FUD.03 当函数参数出现太多 bool 类型的参数时,应该考虑将其封装为自定义的结构体或枚举 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 布尔类型的参数过多,很难让人记住,容易出错。将其封装为枚举或结构体,可以更好地利用类型系统的检查而避免出错。 8 | 其他类型参数过多时,也可以考虑是否可以用自定义结构体或枚举进行封装。 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | #![warn(clippy::fn_params_excessive_bools)] 14 | 15 | // 不符合 16 | fn f(is_round: bool, is_hot: bool) { ... } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | #![warn(clippy::fn_params_excessive_bools)] 23 | 24 | enum Shape { 25 | Round, 26 | Spiky, 27 | } 28 | 29 | enum Temperature { 30 | Hot, 31 | IceCold, 32 | } 33 | 34 | // 符合 35 | fn f(shape: Shape, temperature: Temperature) { ... } 36 | ``` 37 | 38 | **【Lint 检测】** 39 | 40 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 41 | | ------------------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 42 | | [fn_params_excessive_bools](https://rust-lang.github.io/rust-clippy/master/#fn_params_excessive_bools) | yes | no | pedantic | allow | 43 | 44 | 该 lint 对应 `clippy.toml` 配置项: 45 | 46 | ```toml 47 | # 用于配置函数可以拥有的 bool 类型参数最大数量,默认为 3。 48 | max-fn-params-bools = 3 49 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/G.FUD.05.md: -------------------------------------------------------------------------------- 1 | ## G.FUD.05 不要总是为函数指定 `inline(always)` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | `inline` 虽然可以提升性能,但也会增加编译时间和编译大小。 8 | 9 | Rust 中性能、编译时间和编译大小之间需要权衡。根据需要再 `inline` 即可。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | #![warn(clippy::inline_always)] 15 | 16 | // 不符合 17 | #[inline(always)] 18 | fn not_quite_hot_code(..) { ... } 19 | ``` 20 | 21 | **【例外】** 22 | 23 | 根据需要再inline即可,比如明确知道某个函数被调用次数非常频繁,这个时候为了性能考虑要为其手工指定内联。 24 | 25 | ```rust 26 | // 符合:实现内存回收功能,调用非常频繁。性能优先。 27 | #[inline(always)] 28 | pub fn buf_recycle(buf_id: usize) { 29 | // ... 30 | } 31 | ``` 32 | 33 | **【Lint 检测】** 34 | 35 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 36 | | ------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 37 | | [inline_always](https://rust-lang.github.io/rust-clippy/master/#inline_always) | yes | no | pedantic | allow | 38 | 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/P.FUD.01.md: -------------------------------------------------------------------------------- 1 | ## P.FUD.01 传递到闭包的变量建议单独重新绑定 2 | 3 | **【描述】** 4 | 5 | 默认情况下,闭包通过借用来捕获环境变量。或者,可以使用 `move` 关键字来移动环境变量到闭包中。 6 | 7 | 将这些要在闭包内用的变量,重新进行分组绑定,可读性更好。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | use std::rc::Rc; 13 | 14 | let num1 = Rc::new(1); 15 | let num2 = Rc::new(2); 16 | let num3 = Rc::new(3); 17 | let closure = { 18 | // `num1` 所有权已经转移 19 | let num2 = num2.clone(); 20 | let num3 = num3.as_ref(); 21 | move || { 22 | *num1 + *num2 + *num3; // 不符合 23 | } 24 | }; 25 | ``` 26 | 27 | **【正例】** 28 | 29 | ```rust 30 | use std::rc::Rc; 31 | 32 | let num1 = Rc::new(1); 33 | let num2 = Rc::new(2); 34 | let num3 = Rc::new(3); 35 | // 符合: 单独对要传递到闭包的变量重新绑定 36 | let num2_cloned = num2.clone(); 37 | let num3_borrowed = num3.as_ref(); 38 | let closure = move || { 39 | *num1 + *num2_cloned + *num3_borrowed; // 符合 40 | }; 41 | ``` 42 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/fn-design/P.FUD.02.md: -------------------------------------------------------------------------------- 1 | ## P.FUD.02 函数返回值不要使用 `return` 2 | 3 | **【描述】** 4 | 5 | Rust 中函数块会自动返回最后一个表达式的值,不需要显式地指定 `return`。 6 | 7 | 只有在函数过程中需要提前返回的时候再加 Return。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | fn foo(x: usize) -> usize { 13 | if x < 42{ 14 | return x; 15 | } 16 | return x + 1; // 不符合 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | fn foo(x: usize) -> usize { 24 | if x < 42{ 25 | return x; 26 | } 27 | x + 1 // 符合 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic.md: -------------------------------------------------------------------------------- 1 | # 3.10 泛型 2 | 3 | Rust 中的泛型允许开发人员编写更加简洁、更少重复的代码。但泛型可能会引起编译文件大小膨胀,酌情使用。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic/G.GEN.01.md: -------------------------------------------------------------------------------- 1 | 2 | ## G.GEN.01 不要在泛型位置上使用内建类型 3 | 4 | **【级别】** 建议 5 | 6 | **【描述】** 7 | 8 | 这样做虽然会导致编译错误,但是这种错误会使开发者感到困惑,反而无法找到问题所在。 9 | 10 | **【反例】** 11 | 12 | 这里 `u32` 会被认为是一个类型参数。 13 | 14 | ```rust 15 | // 不符合 16 | impl Foo { 17 | fn impl_func(&self) -> u32 { 18 | 42 19 | } 20 | } 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | // 符合 27 | impl Foo { 28 | fn impl_func(&self) -> T { 29 | 42 30 | } 31 | } 32 | ``` 33 | 34 | **【Lint 检测】** 35 | 36 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 37 | | ------ | ---- | --------- | ------ | ------ | 38 | | [builtin_type_shadow](https://rust-lang.github.io/rust-clippy/master/#builtin_type_shadow) | yes| no | style | warn | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic/G.GEN.02.md: -------------------------------------------------------------------------------- 1 | ## G.GEN.02 使用 Rust 标准库中某些方法,要注意避免使用其泛型默认实现,而应该使用具体类型的实现 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 标准库内部某些类型使用了 泛型特化(未稳定特性),比如 `ToString` trait。 8 | 9 | 该 trait 有一个[泛型默认实现](https://doc.rust-lang.org/stable/src/alloc/string.rs.html#2369), 并且一些具体类型也实现了它,比如 `char`/ `str` / `u8`/ `i8` 等。 10 | 11 | 在实际代码中,应该选择去调用具体类型实现的 `to_string()` 方法,而非调用泛型的默认实现。 12 | 13 | **这一规则要求开发者对 Rust 标准库的一些方法实现有一定了解。** 14 | 15 | **【反例】** 16 | 17 | ```rust 18 | #![warn(clippy::inefficient_to_string)] 19 | 20 | // 不符合 21 | // 闭包参数中, s 为 `&&str` 类型 22 | // `&&str` 就会去调用泛型的默认实现 23 | ["foo", "bar"].iter().map(|s| s.to_string() ); 24 | ``` 25 | 26 | **【正例】** 27 | 28 | ```rust 29 | #![warn(clippy::inefficient_to_string)] 30 | 31 | // 符合 32 | // 闭包参数中, s 为 `&&str` 类型,使用 `|&s|` 对参数模式匹配后,闭包体内 `s` 就变成了 `&str` 类型 33 | // 经过这样的转换,直接调用 `&str`的 `to_string()` 方法,而如果是 `&&str` 就会去调用泛型的默认实现。 34 | ["foo", "bar"].iter().map(|&s| s.to_string() ); 35 | ``` 36 | 37 | **【Lint 检测】** 38 | 39 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 40 | | ------------------------------------------------------------ | ------------- | ------------ | ---------- | ----- | 41 | | [inefficient_to_string](https://rust-lang.github.io/rust-clippy/master/#inefficient_to_string) | yes | no | pedantic | allow | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic/P.GEN.02.md: -------------------------------------------------------------------------------- 1 | ## P.GEN.02 不要随便使用 `impl Trait` 语法替代泛型限定 2 | 3 | **【描述】** 4 | 5 | `impl Trait` 语法 和 泛型限定,虽然都是静态分发,且效果类似,但是它们的语义是不同的。 6 | 7 | **在类型系统层面上的语义:** 8 | 9 | 1. `impl Trait` 是 存在量化类型。意指,存在某一个被限定的类型。 10 | 2. 泛型限定 是 通用量化类型。意指,所有被限定的类型。 11 | 12 | 要根据它们的语义来选择不同的写法。 13 | 14 | 另外,`impl Trait` 可以用在函数参数位置和返回值位置,但是不同位置意义不同。 15 | 16 | **函数参数位置** 17 | 18 | 等价于 泛型参数。 19 | 20 | 但要注意: 21 | 22 | ```rust 23 | fn f(b1: impl Bar, b2: impl Bar) -> usize 24 | ``` 25 | 26 | 等价于: 27 | 28 | ```rust 29 | fn f(b1: B1, b2: B2) -> usize 30 | ``` 31 | 32 | 而不是 33 | 34 | ```rust 35 | fn f(b1: B, b2: B) -> usize 36 | ``` 37 | 38 | 证明示例: 39 | 40 | ```rust 41 | use std::fmt::Display; 42 | 43 | // 函数参数可以传入 整数,但是函数返回值是 String 44 | fn func(arg: impl Display) -> impl Display { 45 | format!("Hay! I am not the same as \"{}\"", arg) 46 | } 47 | 48 | // 很明显不等价于下面这类 49 | // fn somefunc2(arg: T) -> T { 50 | // // 需要指定同一个类型 T 的行为 51 | // } 52 | 53 | fn main(){ 54 | let a = 42; 55 | let a = func(42); 56 | } 57 | ``` 58 | 59 | **函数返回值** 60 | 61 | 在返回值位置上,如果是泛型参数,则是由调用者来选择具体类型,比如 `parse::("32")` ; 如果是 `impl Trait`,则是由被调用者来决定具体类型,但只能有一种类型。 62 | 63 | 在返回值位置上的 `impl Trait` 会根据函数体的返回值自动推断实现了哪些 auto trait。这意味着你不必在 `impl Trait` 后面再 加 `Sync + Send ` 这种auto trait。 64 | 65 | 注意下面代码: 66 | 67 | ```rust 68 | // Error: 这里只允许有同一种具体类型,Foo 和 Baz 都实现了 Bar 也是错的。 69 | fn f(a: bool) -> impl Bar { 70 | if a { 71 | Foo { ... } 72 | } else { 73 | Baz { ... } 74 | } 75 | } 76 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic/P.GEN.03.md: -------------------------------------------------------------------------------- 1 | ## P.GEN.03 不要使用太多泛型参数和 trait 限定,否则会增长编译时间 2 | 3 | **【描述】** 4 | 5 | 为泛型函数添加详细的 trait 限定,可以在一定程度上增强用户使用体验,但使用过多的泛型参数和 trait 限定会显著地增长编译时间。 6 | 7 | **【反例】** 8 | 9 | 此写法比正例的写法编译时间要多十倍。 10 | 11 | ```rust 12 | // From: https://github.com/tokio-rs/axum/pull/198 13 | fn handle_error( 14 | self, 15 | f: F, 16 | ) -> HandleError 17 | where 18 | Self: Service, Response = Response>, 19 | F: FnOnce(Self::Error) -> Result, 20 | Res: IntoResponse, 21 | ResBody: http_body::Body + Send + Sync + 'static, 22 | ResBody::Error: Into + Send + Sync + 'static, 23 | { 24 | HandleError::new(self, f) 25 | } 26 | ``` 27 | 28 | **【正例】** 29 | 30 | 来自于 Web 框架 Axum 的代码: 31 | 32 | ```rust 33 | // From: https://github.com/tokio-rs/axum/pull/198 34 | fn handle_error( 35 | self, 36 | f: F, 37 | ) -> HandleError { 38 | HandleError::new(self, f) 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/generic/P.GEN.05.md: -------------------------------------------------------------------------------- 1 | ## P.GEN.05 定义泛型函数时,如果该函数实现用到来自 trait 定义的相关行为,需要为泛型指定相关 trait 的限定 2 | 3 | **【描述】** 4 | 5 | 泛型,在 Rust 类型系统中的语义是一种 通用量化类型(Universally-quantified type),即,泛型类型` T` 的所有可能 的单态类型。 6 | 7 | 在泛型函数内部,如果使用了来自某个 trait 定义的行为,则需要为泛型指定相关的 trait 限定,来排除其他没有实现该trait 的类型。 8 | 9 | > 注:Rust编译器可以检测这种情况,但是编译错误比较晦涩,本原则用来提示开发者注意这种情况。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | use std::fmt; 15 | 16 | // println! 中 `{:?}` 为 Debug triat 定义行为 17 | fn some_func(foo: T) { 18 | println!("{:?}", foo); // error[E0277]: `T` doesn't implement `Debug` 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | use std::fmt; 26 | 27 | // 为泛型类型 T 指派 Debug triat 限定 28 | fn some_func(foo: T) { 29 | println!("{:?}", foo); 30 | } 31 | 32 | struct A; 33 | 34 | fn main() { 35 | some_func(5i32); 36 | 37 | // A 没有实现 Debug trait,会被排除掉 38 | some_func(A); // error[E0277]: `A` doesn't implement `Debug` 39 | } 40 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/io.md: -------------------------------------------------------------------------------- 1 | # 3.21 Safe I/O 2 | 3 | 在标准库中也提供了标准 I/O 类型,在 Safe Rust 下,I/O 操作是足够安全的,但是对于 原生句柄 (Raw Fd) 的操作,则属于不安全。 4 | 5 | 在 Unsafe Rust 下也有相关 I/O 的规范,请参考 [Unsafe Rust - I/O](./unsafe_rust/io.md) 部分。 6 | 7 | 本部分只关注 Safe Rust 下 I/O 相关规范。 8 | 9 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/io/G.FIO.01.md: -------------------------------------------------------------------------------- 1 | ## G.FIO.01 文件读取建议使用 `BufReader/BufWriter` 来代替 `Reader/Write` 2 | 3 | **【描述】** 4 | 5 | `BufReader/BufWriter` 使用缓冲区来减少 I/O 请求的次数,提升性能。访问磁盘一次读取 256 个字节显然比 访问磁盘256次每次一个字节 效率要更高。 6 | 7 | **【示例】** 8 | 9 | ```rust 10 | use std::fs::File; 11 | use std::io::{BufReader, Read}; 12 | 13 | fn main() { 14 | let mut data = String::new(); 15 | let f = File::open("/etc/hosts").expect("Unable to open file"); 16 | let mut br = BufReader::new(f); 17 | br.read_to_string(&mut data).expect("Unable to read string"); 18 | println!("{}", data); 19 | } 20 | ``` 21 | 22 | 写 I/O: 23 | 24 | ```rust 25 | use std::fs::File; 26 | use std::io::{BufWriter, Write}; 27 | 28 | fn main() { 29 | let data = "Some data!"; 30 | let f = File::create("/tmp/foo").expect("Unable to create file"); 31 | let mut f = BufWriter::new(f); 32 | f.write_all(data.as_bytes()).expect("Unable to write data"); 33 | } 34 | ``` 35 | 36 | 逐行读: 注意返回的每行字符串都不含有换行字符。 37 | 38 | ```rust 39 | use std::fs::File; 40 | use std::io::{BufRead, BufReader}; 41 | 42 | pub fn scan() -> Result<(), io::Error> { 43 | let mut file = BufReader::new(try!(File::open("foo.txt"))); 44 | 45 | let mut line = String::new(); 46 | while try!(file.read_line(&mut line)) != 0 { 47 | if line.starts_with("x") { 48 | try!(file.seek(SeekFrom::Start(1000))); 49 | } 50 | do_stuff(&line); 51 | line.clear(); 52 | } 53 | 54 | Ok(()) 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/io/P.FIO.01.md: -------------------------------------------------------------------------------- 1 | ## P.FIO.01 使用 `read_to_end/read_to_string`方法时注意文件的大小能否一次性读入内存中 2 | 3 | **【描述】** 4 | 5 | 对于内存可以一次性读完的文件,可以使用 `read_to_end/read_to_string`之类的方法。但是如果你想读任意大小的文件,则不适合使用它们。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros.md: -------------------------------------------------------------------------------- 1 | # 3.16 宏 2 | 3 | Rust 通过宏来支持元编程。其中宏有很多种,按实现方式可以分为两大类:声明宏(Declarative)和 过程宏(Procedural)。 4 | 5 | 按功能效果,过程宏又可以分为三类: 6 | 7 | 1. Function-like 宏。类似于声明宏那样,像函数调用一样去使用的宏。 8 | 2. Derive 宏。用于为数据类型自动生成一些 语法项(item),比如 trait 、结构体、方法等。 9 | 3. Attribute 宏。用于更加通用的代码生成功能。 10 | 11 | Rust 语言核心库和标准库都内置了一些声明宏和过程宏,以方便开发者使用。 12 | 13 | 内置的属性宏按功能大体又可以分为四类: 14 | > 注:属性宏固定语法为 `#[attr]` 或 `#![attr]`,以下使用用例均已简化为 `attr` 的形式。即 `test`, `allow(c)` 代表其在 Rust 内的实现可分别表现为 `#[test]` 及 `#[allow(c)]`。 15 | 16 | 1. 测试属性。`test` 属性宏用于将某个函数标记为单元测试函数。 17 | 2. 诊断([Diagnostic](https://doc.rust-lang.org/reference/attributes/diagnostics.html#diagnostic-attributes))属性。用于在编译过程中控制和生成诊断信息。包括: 18 | 1. `allow(c)` / `warn(c)` / `deny(c)` / `forbid(c)` 等。 19 | 2. `must_use` 。 20 | 3. [代码生成属性](https://doc.rust-lang.org/reference/attributes/codegen.html)。包括:`inline` / `cold` / `target_feature` 等。 21 | 4. [编译时限制属性](https://doc.rust-lang.org/reference/attributes/limits.html)。包括:`recursion_limit ` / `type_length_limit` 。 22 | 5. [类型系统属性](https://doc.rust-lang.org/reference/attributes/type_system.html)。包括:`non_exhaustive` 。 23 | 24 | **宏编程规范:** 25 | 26 | 使用宏时,需要从 `声明宏` 和 `过程宏` 各自的特性为出发点,来安全使用它。 27 | 28 | - [声明宏规范](./macros/decl.md) 29 | - [过程宏规范](./macros/proc.md) 30 | 31 | **宏展开命令:** 32 | 33 | ```text 34 | # 对单个 rs 文件 35 | rustc -Z unstable-options --pretty expanded hello.rs 36 | # 对项目里的二进制 rs 文件 37 | cargo rustc --bin hello -- -Z unstable-options --pretty=expanded 38 | ``` 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/G.MAC.01.md: -------------------------------------------------------------------------------- 1 | ## G.MAC.01 `dbg!()` 宏只应该用于调试代码 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | `dbg!()` 宏是 Rust 内置的宏,其目的是用于调试代码。 不要将含有 dbg! 宏的代码加入到版本控制下。 8 | 9 | 注意:不管在 Debug 模式还是 Release 模式下,调试信息都会被打印出来。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | #![warn(clippy::dbg_macro)] 15 | // 不符合:代码加入版本控制时还保留着 dbg! 代码 16 | let foo = false; 17 | dbg!(foo); 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | #![warn(clippy::dbg_macro)] 24 | 25 | // 符合:代码加入版本控制时注释掉 dbg! 代码 26 | let foo = false; 27 | // dbg!(foo); 28 | ``` 29 | 30 | **【Lint 检测】** 31 | 32 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 33 | | ---------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 34 | | [dbg_macro](https://rust-lang.github.io/rust-clippy/master/#dbg_macro) | yes | no | restriction | allow | 35 | 36 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/G.MAC.02.md: -------------------------------------------------------------------------------- 1 | ## G.MAC.02 使用宏时应该考虑宏展开会让编译文件膨胀的影响 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在多个地方使用 `println!` 或 `panic!` 之类的内置宏时,可以将其包装到函数内,使用 `#[cold]` 和 `#[inline(never)]` 属性避免其内联,从而避免编译文件膨胀。 8 | 9 | 因为像 `println!` 或 `panic!` 之类的宏,如果到处使用,就会到处展开代码,会导致编译文件大小膨胀。尤其在嵌入式领域需要注意。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | pub fn expect(self, msg: &str) -> T { 15 | match self { 16 | Ok(t) => t, 17 | Err(e) => panic!("{}: {:?}", msg, &e), // 不符合 18 | } 19 | } 20 | 21 | pub fn unwrap_err(self) -> E { 22 | match self { 23 | Ok(t) => panic!("{}: {:?}", "called `Result::unwrap_err()` on an `Ok` value", &t), // 不符合 24 | Err(e) => e, 25 | } 26 | } 27 | ``` 28 | 29 | **【正例】** 30 | 31 | ```rust 32 | #[inline(never)] 33 | #[cold] 34 | #[track_caller] // 为了定位 panic 发生时的调用者的位置 35 | fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! { 36 | panic!("{}: {:?}", msg, error) 37 | } 38 | 39 | pub fn expect(self, msg: &str) -> T { 40 | match self { 41 | Ok(t) => t, 42 | Err(e) => unwrap_failed(msg, &e), // 符合 43 | } 44 | } 45 | 46 | pub fn unwrap_err(self) -> E { 47 | match self { 48 | Ok(t) => unwrap_failed("called `Result::unwrap_err()` on an `Ok` value", &t), // 符合 49 | Err(e) => e, 50 | } 51 | } 52 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/P.MAC.01.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.01 不要轻易使用宏 2 | 3 | **【描述】** 4 | 5 | 当一个开发者想要能写出强大且用户友好的宏API时,不仅需要掌握如何用宏去实现,更需要掌握宏之外关于 Rust 的一切。 6 | 7 | 宏设计的重点在于宏生成什么样的代码,而不是宏如何生成代码。 8 | 9 | 宏只是将 Rust 语言特性以一种有趣的方式组合在一起能自动生成代码的创造力。 10 | 11 | 尤其是过程宏,它有一定复杂性,且很难调试,不卫生,也容易出错,不适用于新手。 12 | 13 | > "卫生" 这个词表示,宏展开后,不会污染原来的词法作用域。 14 | 15 | **【参考】** 16 | 17 | [Rust 社区顶级专家 David Tolnay 写的宏学习案例](https://github.com/dtolnay/case-studies) 18 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/P.MAC.02.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.02 实现宏语法的时候,应该尽量贴近 Rust 语法 2 | 3 | **【描述】** 4 | 5 | Rust 宏可以让开发者定义自己的 DSL,但是在使用宏的时候,要尽可能贴近 Rust 的语法。这样可以增强可读性,让其他开发者在使用宏的时候,可以猜测出它生成的代码。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 不符合:无关键词 11 | bitflags! { 12 | S: u32 { /* ... */ } 13 | } 14 | 15 | // 不符合:或使用一些自定义的特定用途关键词 16 | bitflags! { 17 | flags S: u32 { /* ... */ } 18 | } 19 | 20 | // 或 21 | bitflags! { 22 | struct S: u32 { 23 | const E = 0b010000, // 不符合:结尾应该是分号更符合 Rust 语法 24 | const F = 0b100000, 25 | } 26 | } 27 | ``` 28 | 29 | **【正例】** 30 | 31 | ```rust 32 | bitflags! { 33 | struct S: u32 { /* ... */ } // 符合 34 | } 35 | 36 | // 符合:结尾是正确的分号 37 | bitflags! { 38 | struct S: u32 { 39 | const C = 0b000100; 40 | const D = 0b001000; 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl.md: -------------------------------------------------------------------------------- 1 | # 声明宏 2 | 3 | [声明宏](https://doc.rust-lang.org/reference/macros-by-example.html) 也被叫做 示例宏(macros by example),或者简单地叫做 宏。目前声明宏使用 `macro_rules!`来定义。 4 | 5 | 声明宏的特点是:它只用作代码替换,而无法进行计算。 6 | 7 | ## 列表 8 | 9 | - [P.MAC.DCL声明宏内的变量作为外部变量使用](./decl/P.MAC.DCL.01.md) 10 | - [P.MAC.DCL.02 在编写多个宏规则时,应该先从匹配粒度最小的开始写](./decl/P.MAC.DCL.02.md) 11 | - [P.MAC.DCL.03 不要在片段分类符跟随它不匹配的符号](./decl/P.MAC.DCL.03.md) 12 | - [P.MAC.DCL.04 匹配规则要精准,不要模糊不清](./decl/P.MAC.DCL.04.md) 13 | - [P.MAC.DCL.05 使用宏替换(substitution)元变量的时候要注意选择合适的片段分类符](./decl/P.MAC.DCL.05.md) 14 | - [P.MAC.DCL.06 当宏需要接收 self 时需要注意](./decl/P.MAC.DCL.06.md) 15 | - [P.MAC.DCL.07 确保在宏定义之后再去调用宏](./decl/P.MAC.DCL.07.md) 16 | - [P.MAC.DCL.08 同一个 crate 内定义的宏相互调用时,需要注意卫生性](./decl/P.MAC.DCL.08.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.02.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.02 在编写多个宏规则时,应该先从匹配粒度最小的开始写 2 | 3 | **【描述】** 4 | 5 | 因为声明宏中,是按规则的编写顺序来匹配的。当第一个规则被匹配到,后面的规则将永远不会匹配到。所以,编写声明宏规则时,需要先写匹配粒度最小的,最具体的规则,然后逐步编写匹配范围更广泛的规则。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | #[macro_export] 11 | macro_rules! foo { 12 | (@as_expr $e:expr) => {$e}; // expr 比 tt 匹配更加具体 13 | 14 | ($($tts:tt)*) => { 15 | foo!(@as_expr $($tts)*) 16 | }; 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.03.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.03 不要在片段分类符跟随它不匹配的符号 2 | 3 | **【描述】** 4 | 5 | `macro_rules!` 定义声明宏时,非终止的元变量匹配必须紧随一个已被决定能在这种匹配之后安全使用的标记。 6 | 7 | 具体的规则参见:[Follow-set Ambiguity Restrictions](https://doc.rust-lang.org/reference/macros-by-example.html#follow-set-ambiguity-restrictions) 8 | 9 | 片段分类符([fragment-specifier](https://doc.rust-lang.org/nightly/reference/macros-by-example.html#metavariables))的说明参见[附录B:术语解释](./../../../Appendix/terms.md) 10 | 11 | **【反例】** 12 | 13 | 对于 `[,]` 这样的分隔标记就是非法的。这是为了防止未来 Rust 语法变动导致宏定义失效。 14 | 15 | ```rust 16 | #[macro_export] 17 | macro_rules! foo { 18 | ($e1:expr [,] $e2:expr) => {$e1; $e2}; 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | 该示例中,元变量 `$e1` 的 片段分类符 `expr` 是非终止的,所以后面需要跟随一个用于分隔的标记。 25 | 26 | Rust 规定在 `expr` 片段分类符 后面可以合法地跟随 `=>` / `,` / `;` 。 27 | 28 | ```rust 29 | #[macro_export] 30 | macro_rules! foo { 31 | ( $e1:expr, $e2:expr) => {$e1; $e2}; 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.04.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.04 匹配规则要精准,不要模糊不清 2 | 3 | **【描述】** 4 | 5 | 匹配规则必须精准,因为宏解析器并不会去执行代码,它无法匹配模糊不清的规则。 6 | 7 | **【反例】** 8 | 9 | 宏解析器无法确定第一次匹配的应该是多少个 `ident`。 10 | 11 | ```rust 12 | macro_rules! ambiguity { 13 | ($($i:ident)* $i2:ident) => { }; 14 | } 15 | 16 | // error: 17 | // local ambiguity: multiple parsing options: built-in NTs ident ('i') or ident ('i2'). 18 | fn main() { ambiguity!(an_identifier); } 19 | ``` 20 | 21 | **【正例】** 22 | 23 | ```rust 24 | macro_rules! ambiguity { 25 | ($i2:ident $($i:ident)* ) => { }; 26 | } 27 | 28 | // ok 29 | fn main() { ambiguity!(an_identifier an_identifier2); } 30 | ``` 31 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.06.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.06 当宏需要接收 `self` 时需要注意 2 | 3 | **【描述】** 4 | 5 | `self` 在 Rust 中属于关键字,它会在代码运行时被替换为具体类型的实例。当它传递给 宏 时会被看做为一个变量,而宏对于变量而言是具备卫生性的。而且,声明宏的作用只是替换而非计算,它并不能计算出 self 的具体类型。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | macro_rules! make_mutable { 11 | ($i:ident) => {let mut $i = $i;}; 12 | } 13 | 14 | struct Dummy(i32); 15 | 16 | impl Dummy { 17 | fn double(self) -> Dummy { 18 | make_mutable!(self); // 这里传入的 self 和宏内部 let 定义的 self 不同 19 | self.0 *= 2; 20 | self 21 | } 22 | } 23 | 24 | fn main() { 25 | println!("{:?}", Dummy(4).double().0); 26 | } 27 | ``` 28 | 29 | **【正例】** 30 | 31 | ```rust 32 | macro_rules! double_method { 33 | ($self_:ident, $body:expr) => { 34 | fn double(mut $self_) -> Dummy { 35 | $body 36 | } 37 | }; 38 | } 39 | 40 | struct Dummy(i32); 41 | 42 | impl Dummy { 43 | double_method! {self, { 44 | self.0 *= 2; 45 | self 46 | }} 47 | } 48 | 49 | fn main() { 50 | println!("{:?}", Dummy(4).double().0); 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.07.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.07 确保在宏定义之后再去调用宏 2 | 3 | **【描述】** 4 | 5 | Rust 中类型或函数在定义前后都可以调用,但是宏不一样。Rust 查找宏定义是按词法依赖顺序的,必须注意定义和调用的先后顺序。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | mod a { 11 | // X!(); // undefined 12 | } 13 | 14 | mod b { 15 | // X!(); // undefined 16 | macro_rules! X { () => {}; } 17 | X!(); // defined 18 | } 19 | 20 | mod c { 21 | // X!(); // undefined 22 | } 23 | 24 | fn main() {} 25 | ``` 26 | 27 | **【正例】** 28 | 29 | ```rust 30 | macro_rules! X { () => {}; } 31 | 32 | mod a { 33 | X!(); // defined 34 | } 35 | 36 | mod b { 37 | X!(); // defined 38 | } 39 | 40 | mod c { 41 | X!(); // defined 42 | } 43 | 44 | fn main() {} 45 | ``` 46 | 47 | **【例外】** 48 | 49 | 宏与宏之间相互调用,不受词法顺序的限制。 50 | 51 | ```rust 52 | mod a { 53 | // X!(); // undefined 54 | } 55 | 56 | macro_rules! X { () => { Y!(); }; } // 注意:这里的 Y! 宏是在定义前被调用的,代码正常执行 57 | 58 | mod b { 59 | // X!(); // defined, but Y! is undefined 60 | } 61 | 62 | macro_rules! Y { () => {}; } // Y! 宏被定义在 X! 宏后面 63 | 64 | mod c { 65 | X!(); // defined, and so is Y! 66 | } 67 | fn main() {} 68 | ``` 69 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/decl/P.MAC.DCL.08.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.DCL.08 同一个 crate 内定义的宏相互调用时,需要注意卫生性 2 | 3 | **【描述】** 4 | 5 | 当同一个 crate 内定义的宏相互调用时候,应该使用 `$crate` 元变量来指代当前被调用宏的路径。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | #[macro_export] 11 | macro_rules! helped { 12 | () => { helper!() } // This might lead to an error due to 'helper' not being in scope. 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! helper { 17 | () => { () } 18 | } 19 | 20 | //// 在另外的 crate 中使用这两个宏 21 | // 注意:`helper_macro::helper` 并没有导入进来 22 | use helper_macro::helped; 23 | 24 | fn unit() { 25 | // Error! 这个宏会出现问题,因为其内部调用的 helper 宏的路径会被编译器认为是当前调用crate 的路径 26 | helped!(); 27 | } 28 | ``` 29 | 30 | **【正例】** 31 | 32 | ```rust 33 | #[macro_export] 34 | macro_rules! helped { 35 | () => { $crate::helper!() } 36 | } 37 | 38 | #[macro_export] 39 | macro_rules! helper { 40 | () => { () } 41 | } 42 | 43 | //// 在另外的 crate 中使用这两个宏 44 | // 注意:`helper_macro::helper` 并没有导入进来 45 | use helper_macro::helped; 46 | 47 | fn unit() { 48 | // OK! 这个宏能运行通过,因为 `$crate` 正确地展开成 `helper_macro` crate 的路径(而不是使用者的路径) 49 | helped!(); 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/proc.md: -------------------------------------------------------------------------------- 1 | # 过程宏 2 | 3 | [过程宏(*Procedural macros*)](https://doc.rust-lang.org/reference/procedural-macros.html) 允许开发者来创建语法扩展。你可以通过过程宏创建类似函数的宏、派生宏以及属性宏。 4 | 5 | 广义上的"过程宏"指的是通过 syn/quote(毕竟几乎全部过程宏库都用 syn) 及 syn 生态(例如 darling) 进行代码生成等元编程操作。 6 | 7 | syn/quote 不仅能用于过程宏,还广泛用于代码生成(*codegen*)、静态分析等用途,例如 tonic-build/prost 源码中也用到了 syn/quote 。 8 | 9 | 因此本过程宏规范不仅适用于过程宏,部分规范(例如 [P.MAC.PRO.06](./proc/P.MAC.PRO.06.md)) 还适用于 prost 这种代码生成库 10 | 11 | 过程宏必须被单独定义在一个类型为`proc-macro` 的 crate 中。 12 | 13 | 过程宏有两类报告错误的方式:`panic` 或 通过 `compile_error` 宏调用发出错误。 14 | 15 | 过程宏不具有卫生性(hygiene),这意味着它会受到外部语法项(item)的影响,也会影响到外部导入。 16 | 17 | 过程宏可以在编译期执行任意代码。 18 | 19 | ## 列表 20 | 21 | - [P.MAC.PRO.01 不要使用过程宏来规避静态分析检查](./proc/P.MAC.PRO.01.md) 22 | - [P.MAC.PRO.02 实现过程宏时要对关键特性增加测试](./proc/P.MAC.PRO.02.md) 23 | - [P.MAC.PRO.03 保证过程宏的卫生性](./proc/P.MAC.PRO.03.md) 24 | - [P.MAC.PRO.04 给出正确的错误位置](./proc/P.MAC.PRO.04.md) 25 | - [P.MAC.PRO.05 代码生成要按情况选择使用过程宏还是 build.rs](./proc/P.MAC.PRO.05.md) 26 | - [P.MAC.PRO.06 build.rs 生成的代码要保证没有任何警告](./proc/P.MAC.PRO.06.md) 27 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/proc/P.MAC.PRO.02.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.PRO.02 实现过程宏时要对关键特性增加测试 2 | 3 | **【描述】** 4 | 5 | 实现过程宏的时候,要对关键特性增加测试,这是为了避免出现关键特性遗漏的情况。 6 | 7 | **【反例】** 8 | 9 | 在第三方库 [zeroize](https://github.com/iqlusioninc/crates/tree/main/zeroize) 中,曾经因为过程宏中对枚举类型没有实现 Drop 而引起问题。参见:[RUSTSEC-2021-0115](https://rustsec.org/advisories/RUSTSEC-2021-0115.html) 10 | 11 | ```rust 12 | #[derive(Zeroize)] 13 | #[zeroize(drop)] 14 | pub enum Fails { 15 | Variant(Vec), 16 | } 17 | 18 | // This does compile with zeroize_derive version 1.1, meaning `#[zeroize(drop)]` didn't implement `Drop`. 19 | impl Drop for Fails { 20 | fn drop(&mut self) { 21 | todo!() 22 | } 23 | } 24 | ``` 25 | 26 | **【正例】** 27 | 28 | 在第三方库 [zeroize](https://github.com/iqlusioninc/crates/tree/main/zeroize) 中,曾经因为过程宏中对枚举类型没有实现 Drop 而引起问题。增加关键性测试可以避免这类问题。 29 | 30 | ```rust 31 | #[test] 32 | fn zeroize_on_struct() { 33 | parse_zeroize_test(stringify!( 34 | #[zeroize(drop)] 35 | struct Z { 36 | a: String, 37 | b: Vec, 38 | c: [u8; 3], 39 | } 40 | )); 41 | } 42 | 43 | #[test] 44 | fn zeroize_on_enum() { 45 | parse_zeroize_test(stringify!( 46 | #[zeroize(drop)] 47 | enum Z { 48 | Variant1 { a: String, b: Vec, c: [u8; 3] }, 49 | } 50 | )); 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/proc/P.MAC.PRO.03.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.PRO.03 保证过程宏的卫生性 2 | 3 | **【描述】** 4 | 5 | 过程宏生成的代码尽量使用完全限定名,防止命名冲突产生意想不到的后果。 6 | 7 | 可以使用 `#![no_implicit_prelude]` 属性来验证过程宏的卫生性。 8 | 9 | ```rust 10 | #![no_implicit_prelude] 11 | 12 | #[derive(MyMacro)] 13 | struct A; 14 | ``` 15 | 16 | **【反例】** 17 | 18 | ```rust 19 | quote!(a.to_string()) 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | quote!(std::string::ToString::to_string(a)) 26 | ``` 27 | 28 | ```rust 29 | quote! {{ 30 | use std::string::ToString; 31 | a.to_string() 32 | }} 33 | ``` 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/macros/proc/P.MAC.PRO.04.md: -------------------------------------------------------------------------------- 1 | ## P.MAC.PRO.04 给出正确的错误位置 2 | 3 | **【描述】** 4 | 5 | 过程宏发生错误时,返回的错误应该有正确的位置信息。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 直接用Span::call_site() 11 | Error::new(Span::call_site(), "requires unit variant") 12 | .to_compile_error() 13 | .into() 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | #[proc_macro_derive(MyMacro)] 20 | pub fn derive_my_macro(input: TokenStream) -> TokenStream { 21 | let derive_input: DeriveInput = syn::parse_macro_input!(input as DeriveInput); 22 | 23 | if let Data::Enum(e) = &derive_input.data { 24 | for variant in &e.variants { 25 | if !variant.fields.is_empty() { 26 | // 使用variant的span 27 | return syn::Error::new_spanned(&variant, "must be a unit variable.") 28 | .to_compile_error() 29 | .into(); 30 | } 31 | } 32 | } 33 | 34 | todo!() 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory.md: -------------------------------------------------------------------------------- 1 | # 3.13 内存管理 2 | 3 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/box.md: -------------------------------------------------------------------------------- 1 | # Box 类型 2 | 3 | Rust 中分配堆内存必须要使用的类型,类型签名为 `Box`。 4 | 5 | ## 列表 6 | 7 | - [G.MEM.BOX.01 一般情况下,不应直接对 `Box` 进行借用](./box/G.MEM.BOX.01.md) 8 | - [G.MEM.BOX.02 一般情况下,不应直接对已经在堆上分配内存的类型进行 Box 装箱](./box/G.MEM.BOX.02.md) 9 | - [G.MEM.BOX.03 一般情况下,不应直接对栈分配类型进行 Box 装箱](./box/G.MEM.BOX.03.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/box/G.MEM.BOX.01.md: -------------------------------------------------------------------------------- 1 | ## G.MEM.BOX.01 一般情况下,不应直接对 `Box` 进行借用 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 借用 `Box` 等同于直接借用 `T`,而 `&T` 要比 `&Box` 更常用。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | fn foo(bar: &Box) { ... } 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | // 符合 20 | fn foo(bar: &T) { ... } 21 | ``` 22 | 23 | 24 | **【Lint 检测】** 25 | 26 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 27 | | ---------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 28 | | [borrowed_box](https://rust-lang.github.io/rust-clippy/master/#borrowed_box) | yes | no | complexity | warn | 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/box/G.MEM.BOX.03.md: -------------------------------------------------------------------------------- 1 | ## G.MEM.BOX.03 一般情况下,不应直接对栈分配类型进行 `Box` 装箱 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 此举会对性能造成不必要的影响。只有当某个栈变量太大,需要使用堆分配的情况下,或是栈变量需要逃逸的时候,才需要考虑是否对其使用 `Box` 装箱。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | fn foo(bar: usize) {} 13 | // 不符合 14 | let x = Box::new(1); 15 | foo(*x); 16 | println!("{}", *x); 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | fn foo(bar: usize) {} 23 | // 符合 24 | let x = 1; 25 | foo(x); 26 | println!("{}", x); 27 | ``` 28 | 29 | **【例外】** 30 | 31 | 用例来源:[aitch](https://github.com/mjkillough/aitch/blob/69fbd677a72d0ed1851624d16a53c2a676d49bd5/src/servers/hyper.rs#L28) 32 | 33 | ```rust 34 | pub trait ServeFunc { 35 | fn call_box(self: Box) -> Result<()>; 36 | } 37 | 38 | impl ServeFunc for F 39 | where 40 | F: FnOnce() -> Result<()>, 41 | { 42 | // 特殊情况,F 是泛型,且要匹配 trait定义 43 | #[cfg_attr(feature = "cargo-clippy", allow(boxed_local))] 44 | fn call_box(self: Box) -> Result<()> { 45 | (*self)() 46 | } 47 | } 48 | ``` 49 | 50 | **【Lint 检测】** 51 | 52 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 53 | | -------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 54 | | [boxed_local](https://rust-lang.github.io/rust-clippy/master/#boxed_local) | yes | no | perf | warn | 55 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/drop.md: -------------------------------------------------------------------------------- 1 | # Drop 析构 2 | 3 | 在 Safe Rust 中,Drop 比较安全。在 Unsafe Rust 中则需要注意更多关于 Drop 的问题。 4 | 5 | ## 列表 6 | 7 | - [G.MEM.DRP.01 要注意防范内存泄漏](./drop/G.MEM.DRP.01.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/drop/G.MEM.DRP.01.md: -------------------------------------------------------------------------------- 1 | ## P.MEM.DRP.01 要注意防范内存泄漏 2 | 3 | **【描述】** 4 | 5 | Rust 语言并不保证避免内存泄漏,内存泄漏不属于 Rust 安全职责范围。使用 Rust 的时候需要注意下面情况可能会发生内存泄漏: 6 | 7 | 1. 循环引用导致没有正常调用析构函数 8 | 2. 使用 `forget` / `leak` 等函数主动跳过析构 9 | 3. 使用 `std::mem::ManuallyDrop` 构建数据结构而忘记析构 10 | 4. 析构函数内部发生了 panic 11 | 5. 程序中止(abort on panic) 12 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/lifetime.md: -------------------------------------------------------------------------------- 1 | # 生命周期 2 | 3 | 生命周期(lifetime),也被叫做 生存期。可以理解为引用的有效范围。 4 | 5 | ## 列表 6 | 7 | - [P.MEM.LFT.01 生命周期参数命名尽量有意义且简洁](./lifetime/P.MEM.LFT.01.md) 8 | - [P.MEM.LFT.02 通常需要显式地标注生命周期,而非利用编译器推断](./lifetime/P.MEM.LFT.02.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/lifetime/P.MEM.LFT.01.md: -------------------------------------------------------------------------------- 1 | ## P.MEM.LFT.01 生命周期参数命名尽量有意义且简洁 2 | 3 | **【描述】** 4 | 5 | 生命周期参数的命名应该尽量简单,可以使用表达一定语义的缩写。 6 | 7 | 因为生命周期参数的目的是给编译器使用,用于防止函数中出现悬垂引用。 8 | 9 | 适当简单的携带语义的缩写,可以最小化对业务代码的干扰。并且在生命周期参数较多的情况下,清晰地表达具体哪个引用属于哪个生命周期。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | struct ConstraintGeneration<'a, 'b, 'c> { 15 | infcx: &'c InferCtxt<'b, 'c>, 16 | all_facts: &'a mut Option, 17 | location_table: &'a LocationTable, 18 | liveness_constraints: &'a mut LivenessValues, 19 | borrow_set: &'a BorrowSet<'c>, 20 | body: &'c Body<'c>, 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | // 增加 'cg 意义的文档注释 28 | /// 'cg = the duration of the constraint generation process itself. 29 | struct ConstraintGeneration<'cg, 'cx, 'tcx> { 30 | infcx: &'cg InferCtxt<'cx, 'tcx>, 31 | all_facts: &'cg mut Option, 32 | location_table: &'cg LocationTable, 33 | liveness_constraints: &'cg mut LivenessValues, 34 | borrow_set: &'cg BorrowSet<'tcx>, 35 | body: &'cg Body<'tcx>, 36 | } 37 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/smart-ptr.md: -------------------------------------------------------------------------------- 1 | # 智能指针 2 | 3 | 智能指针,在 Rust 中参与自动管理堆内容、引用计数、抽象指针语义等功能。一般实现了 `Deref` trait 或 `Drop` trait 的类型都可以看作是一种智能指针。 4 | 5 | `Box` 就是一个典型的智能指针,但是因为其在 Rust 中有特殊地位,所以为其单独罗列规则。 6 | 7 | Rust 中常见的智能指针包括: 8 | 9 | - 自动管理堆内存:`Box` 10 | - 引用计数:`Rc / Arc` 11 | - 内部可变性容器:`Cell / RefCell` 12 | 13 | ## 列表 14 | 15 | - [P.MEM.SPT.01 使用 `RefCell` 时宜使用 `try_borrow/try_borrow_mut` 方法](./smart-ptr/P.MEM.SPT.01.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/memory/smart-ptr/P.MEM.SPT.01.md: -------------------------------------------------------------------------------- 1 | ## P.MEM.SPT.01 使用 `RefCell` 时宜使用 `try_borrow/try_borrow_mut` 方法 2 | 3 | **【描述】** 4 | 5 | Rust 的 `RefCell` 在运行时会对通过 `borrow/borrow_mut` 方法借用出去的不可变借用和可变借用进行检查。如果发现违反了借用规则的情况,则会 Panic。 6 | 7 | 所以在一些多线程场景下,开发者可能对细粒度的操作加了锁同步,但是没有对 `RefCell` 进行加锁,此时宜用 `try_borrow/try_borrow_mut` 来代替 `borrow/borrow_mut`,以避免在运行时因为违反借用检查规则而出现 Panic。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | // 以下两个函数会让 C 函数在多线程下调用 14 | // 运行过程中有一定几率会出现 Panic 15 | pub extern "C" fn nic_udrv_suspend() { 16 | NIC_ENTITY.borrow_mut().suspend(); // suspend()需要可变引用 17 | } 18 | 19 | pub extern "C" fn nic_udrv_buf_recycle(buf_id: usize) { 20 | NIC_ENTITY.borrow().buf_recycle(buf_id); // buf_recycle()内有锁可以避免多线程竞争 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | // 符合 28 | // 以下两个函数会让 C 函数在多线程下调用 29 | // 使用 try_borrow 或 try_borrow_mut 可以避免运行过程中出现 Panic 30 | pub extern "C" fn nic_udrv_suspend() { 31 | if let Ok(entity) = NIC_ENTITY.try_borrow_mut() { 32 | entity.suspend(); // suspend()需要可变引用 33 | } 34 | } 35 | 36 | pub extern "C" fn nic_udrv_buf_recycle(buf_id: usize) { 37 | if let Ok(entity) = NIC_ENTITY.try_borrow() { 38 | entity.buf_recycle(buf_id); // buf_recycle()内有锁可以避免多线程竞争 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module.md: -------------------------------------------------------------------------------- 1 | # 3.14 模块 2 | 3 | Rust 中一个文件就是一个模块,也可以通过 `mod` 来创建模块。多个文件放到同一个目录下,也可以成为一个模块。 4 | 5 | 模块相关有三个概念: 6 | 7 | 1. `mod`是 Rust 代码的“骨架”。 8 | 2. `use` 则是用来决定使用或导出哪个模块中的具体的类型或方法。 9 | 3. `Path`,则是一个命名系统,类似于命名空间。 10 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/G.MOD.01.md: -------------------------------------------------------------------------------- 1 | ## G.MOD.01 使用导入模块中的类型或函数,在某些情况下需要带模块名前缀 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 对于标准库中,很多人都熟知的类型 ,比如 `Arc`/ `Rc`/ `Cell`/ `HashMap` 等 , 可以导入它们直接使用。 8 | 9 | 但是对于可能引起困惑的函数,比如 `std::ptr::replace` 和 `std::mem::replace` ,在使用它们的时候,就必须得带上模块前缀。 10 | 11 | 使用一些第三方库中定义的类型或函数,也建议带上crate或模块前缀。如果太长的话,可以考虑使用 `as` 或 `type` 来定义别名。 12 | 13 | 以上考虑都是为了增强代码的可读性、可维护性。 14 | 15 | **【正例】** 16 | 17 | ```rust 18 | use std::sync::Arc; 19 | let foo = Arc::new(vec![1.0, 2.0, 3.0]); // 直接使用 Arc 20 | let a = foo.clone(); 21 | 22 | // 需要带上 ptr 前缀 23 | use std::ptr; 24 | let mut rust = vec!['b', 'u', 's', 't']; 25 | // `mem::replace` would have the same effect without requiring the unsafe 26 | // block. 27 | let b = unsafe { 28 | // 符合 29 | ptr::replace(&mut rust[0], 'r') 30 | }; 31 | ``` 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/G.MOD.02.md: -------------------------------------------------------------------------------- 1 | ## G.MOD.02 如果是作为库供他人使用,在 `lib.rs`中重新导出对外类型、函数和 trait 等 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 这样使用方在使用的时候,就不需要`use crate::mod::mod::struct`,可以直接使用`use crate::struct`,好处是使用方`use`的时候会比较方便和直观。 8 | 9 | **【正例】** 10 | 11 | ```rust 12 | // 符合 13 | // From syn crate 14 | pub use crate::data::{ 15 | Field, Fields, FieldsNamed, FieldsUnnamed, Variant, VisCrate, VisPublic, VisRestricted, 16 | Visibility, 17 | }; 18 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/G.MOD.03.md: -------------------------------------------------------------------------------- 1 | 2 | ## G.MOD.03 导入模块不要随便使用 通配符`*` 3 | 4 | **【级别】** 建议 5 | 6 | **【描述】** 7 | 8 | 使用通配符导入会污染命名空间,比如导入相同命名的函数或类型。 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | #![warn(clippy::wildcard_imports)] 14 | use crate2::*; // Has a function named foo 15 | foo(); // Calls crate1::foo 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | #![warn(clippy::wildcard_imports)] 22 | use crate1::foo; // Imports a function named foo 23 | foo(); // Calls crate1::foo 24 | ``` 25 | 26 | **【例外】** 27 | 28 | ```rust 29 | use prelude::*; 30 | 31 | #[test] 32 | use super::* 33 | ``` 34 | 35 | **【Lint 检测】** 36 | 37 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 38 | | ------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 39 | | [wildcard_imports](https://rust-lang.github.io/rust-clippy/master/#wildcard_imports) | yes | no | pedantic | allow | 40 | 41 | 该 lint 可以通过 clippy 配置项 `warn-on-all-wildcard-imports = false` 来配置,用于是否禁用 `prelude`/ `super` (测试模块中) 使用通配符导入, 默认是 `false`。 42 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/G.MOD.05.md: -------------------------------------------------------------------------------- 1 | ## G.MOD.05 不要在私有模块中设置其内部类型或函数方法为 `pub(crate)` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 如果在私有模块中设置 `pub(crate)` 可能会让使用者产生误解。建议用 `pub` 代替。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | mod internal { 13 | // 不符合 14 | pub(crate) fn internal_fn() { } 15 | } 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | // 符合 22 | mod internal { 23 | // 此函数在模块外部不可见,可以使用 pub 或 继续保持私有 24 | pub fn internal_fn() { } 25 | } 26 | ``` 27 | 28 | 29 | **【Lint 检测】** 30 | 31 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 32 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 33 | | [redundant_pub_crate](https://rust-lang.github.io/rust-clippy/master/#redundant_pub_crate) | yes | no | nursery | allow | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/P.MOD.01.md: -------------------------------------------------------------------------------- 1 | ## P.MOD.01 合理控制对外接口和模块之间的可见性 2 | 3 | **【描述】** 4 | 5 | Rust提供强大的模块(module)系统,并且可以管理这些模块之间的可见性(公有(public)或私有(private))。 6 | 7 | 1、对于提供给其他crate使用的对外函数、结构体、trait等类型需要严格控制对外pub的范围,避免将内部成员对外提供。 8 | 9 | 2、对于crate内部,mod之间可见的类型,需要添加上`pub(crate)`。 10 | 11 | 3、对于mod内部私有的类型,不要添加`pub(crate)`或者`pub`。 12 | 13 | **【正例】** 14 | 15 | ```rust 16 | // lib.rs 17 | pub mod sha512; 18 | pub use sha512::Sha512; 19 | 20 | // sha512.rs 21 | pub struct Sha512 { 22 | inner: Sha512Inner, // inner作为内部结构体,不添加pub描述 23 | } 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/module/P.MOD.02.md: -------------------------------------------------------------------------------- 1 | ## P.MOD.02 将模块的测试移动到单独的文件,有助于增加编译速度 2 | 3 | **【描述】** 4 | 5 | 将模块的测试代码 移到一个单独的文件中,并且用 `#[cfg(test)] 来条件编译 tests 的mod,这样可以减少rebuild和编译时间,在大型项目中很重要。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | src/ 11 | |--codes.rs 12 | |--codes/test.rs 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/no-std.md: -------------------------------------------------------------------------------- 1 | # 3.20 no-std 2 | 3 | `no-std` 是指 被标示为 `#![no_std]` 的 crate,意味着该 crate 将链接到 `core` crate 而非 `std` crate。 4 | 5 | `no-std` 代表 裸机编程,嵌入式 Rust。 6 | 7 | Rust 也有 `#![no_core]` 属性,但是还未稳定,不建议使用。 8 | 9 | > 参考数据: `core` 在编译后文件大小中只占大约 3k 大小。 10 | 11 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/no-std/P.EMB.01.md: -------------------------------------------------------------------------------- 1 | ## P.EMB.01 `no-std` 下必须定义一个Panic行为以确保安全 2 | 3 | **【描述】** 4 | 5 | 鉴于`#![no_std]`应用程序没有标准输出,并且某些`#![no_std]`应用程序(例如嵌入式应用程序)需要不同的 Panic 行为来进行开发和发布。 6 | 7 | 因此,可以通过属性宏`#[panic_handler]`来定义 Panic 行为。 8 | 9 | **【正例】** 10 | 11 | 定义 `panic-semihosting` Crate,将 Panic 消息记录到 Host 的 stderr : 12 | 13 | ```rust 14 | #![no_std] 15 | 16 | use core::fmt::{Write, self}; 17 | use core::panic::PanicInfo; 18 | 19 | struct HStderr { 20 | // .. 21 | } 22 | 23 | #[panic_handler] 24 | fn panic(info: &PanicInfo) -> ! { 25 | let mut host_stderr = HStderr::new(); 26 | 27 | // logs "panicked at '$reason', src/main.rs:27:4" to the host stderr 28 | writeln!(host_stderr, "{}", info).ok(); 29 | 30 | loop {} 31 | } 32 | ``` 33 | 34 | 定义 `panic-halt` Crate,将 Panic 消息丢弃。 35 | 36 | ```rust 37 | #![no_std] 38 | use core::panic::PanicInfo; 39 | 40 | #[panic_handler] 41 | fn panic(_info: &PanicInfo) -> ! { 42 | loop {} 43 | } 44 | ``` 45 | 46 | 在 `app` Crate 中, Debug 和 Release 编译模式调用不同的 Panic 行为。 47 | 48 | ```rust 49 | #![no_std] 50 | 51 | // dev profile 52 | #[cfg(debug_assertions)] 53 | extern crate panic_semihosting; 54 | 55 | // release profile 56 | #[cfg(not(debug_assertions))] 57 | extern crate panic_halt; 58 | 59 | fn main() { 60 | // .. 61 | } 62 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/no-std/P.EMB.02.md: -------------------------------------------------------------------------------- 1 | ## P.EMB.02 no-std 下要确保程序中的类型有正确的内存布局 2 | 3 | **【描述】** 4 | 5 | 链接器决定 no-std 程序的最终内存布局,但我们可以使用[链接器脚本](https://sourceware.org/binutils/docs/ld/Scripts.html)对其进行一些控制。链接器脚本给我们的布局控制粒度是在 段(Section)级别。段是在连续内存中布置的 符号 集合。反过来,符号可以是数据(静态变量)或指令(Rust 函数)。 6 | 7 | 这些编译器生成的符号和段名称不能保证在 Rust 编译器的不同版本中保持不变。但是,Rust 允许我们通过以下属性控制符号名称和部分位置: 8 | 9 | - `#[export_name = "foo"]`将符号名称设置为 `foo`. 10 | - `#[no_mangle]`意思是:使用函数或变量名(不是它的完整路径)作为它的符号名。 `#[no_mangle] fn bar()`将产生一个名为 `bar` 的符号。 11 | - `#[link_section = ".bar"]`将符号放置在名为 `.bar` 的部分中。 12 | 13 | 通过这些属性,我们可以公开程序的稳定 ABI 并在链接描述文件中使用它。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/others.md: -------------------------------------------------------------------------------- 1 | # 3.23 其他 2 | 3 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/others/G.OTH.02.md: -------------------------------------------------------------------------------- 1 | ## G.OTH.02 使用标准库中对应的方法计算秒级、毫秒级、微秒级的时间 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 略。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | # use std::time::Duration; 13 | let dur = Duration::new(5, 0); 14 | 15 | // Bad 16 | let _micros = dur.subsec_nanos() / 1_000; // 不符合:用纳秒计算微秒 17 | let _millis = dur.subsec_nanos() / 1_000_000; // 不符合:用纳秒计算毫秒 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | # use std::time::Duration; 24 | let dur = Duration::new(5, 0); 25 | 26 | // Good 27 | let _micros = dur.subsec_micros(); // 符合:通过标准库函数得到微秒 28 | let _millis = dur.subsec_millis(); // 符合:通过标准库函数得到毫秒 29 | ``` 30 | 31 | **【Lint 检测】** 32 | 33 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 34 | | ------------------------------------------------------------ | ------------- | ------------ | ---------- | ----- | 35 | | [duration_subsec](https://rust-lang.github.io/rust-clippy/master/#duration_subsec) | yes | no | complexity | warn | 36 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/security.md: -------------------------------------------------------------------------------- 1 | # 3.22 Security 2 | 3 | Security 用于规范可能引起信息安全(Security)缺陷的代码实现,而非功能安全( Safety)类问题。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/statics.md: -------------------------------------------------------------------------------- 1 | # 3.2 静态变量 2 | 3 | 静态变量是用 `static` 关键字定义的全局静态变量。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings.md: -------------------------------------------------------------------------------- 1 | # 3.7 字符串 2 | 3 | Rust 中字符串是有效的 UTF-8 编码的字节数组。 4 | 5 | Rust 字符串类型众多,但本节内容主要围绕 :`String` / `&str` 6 | 7 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/G.STR.01.md: -------------------------------------------------------------------------------- 1 | ## G.STR.01 在实现`Display`特质时不应调用`to_string()`方法 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 因为 `to_string` 是间接通过 `Display` 来实现的,如果实现 `Display` 的时候再使用 `to_tring` 的话,将会无限递归。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | use std::fmt; 13 | 14 | struct Structure(i32); 15 | impl fmt::Display for Structure { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | write!(f, "{}", self.to_string()) // 不符合 18 | } 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | use std::fmt; 26 | 27 | struct Structure(i32); 28 | impl fmt::Display for Structure { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | write!(f, "{}", self.0) // 符合 31 | } 32 | } 33 | ``` 34 | 35 | **【Lint 检测】** 36 | 37 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 38 | | -------------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 39 | | [to_string_in_display](https://rust-lang.github.io/rust-clippy/master/#to_string_in_display) | yes | no | correctness | deny | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/G.STR.02.md: -------------------------------------------------------------------------------- 1 | ## G.STR.02 在追加字符串时使用`push_str`方法 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 增强代码的可读性 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::string_add_assign, clippy::string_add)] 13 | 14 | let mut x = "Hello".to_owned(); 15 | x = x + ", World"; // 不符合 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | #![warn(clippy::string_add_assign, clippy::string_add)] 22 | 23 | let mut x = "Hello".to_owned(); 24 | 25 | // More readable 26 | x += ", World"; 27 | x.push_str(", World"); // 符合 28 | ``` 29 | 30 | **【Lint 检测】** 31 | 32 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 33 | | -------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 34 | | [string_add_assign](https://rust-lang.github.io/rust-clippy/master/#string_add_assign) | yes | no | pedantic | allow | 35 | | [string_add](https://rust-lang.github.io/rust-clippy/master/#string_add) | yes | no | restriction | allow | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/G.STR.03.md: -------------------------------------------------------------------------------- 1 | ## G.STR.03 将只包含 `ASCII`字符的字符串字面量转为字节序列可以直接使用`b"str"` 语法代替调用`as_bytes`方法 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 这是为了增强可读性,让代码更简洁。 8 | 9 | 注意,`"str".as_bytes()` 并不等价于 `b"str"`,而是等价于 `&b"str"[..]` 。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | #![warn(clippy::string_lit_as_bytes)] 15 | // 不符合 16 | let bs = "a byte string".as_bytes(); 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | #![warn(clippy::string_lit_as_bytes)] 23 | // 符合 24 | let bs = b"a byte string"; 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 30 | | ------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | --------- | 31 | | [string_lit_as_bytes](https://rust-lang.github.io/rust-clippy/master/#string_lit_as_bytes) | yes | no | nursery | allow | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/G.STR.04.md: -------------------------------------------------------------------------------- 1 | ## G.STR.04 需要辨别字符串的字符开头或结尾字符时,不应按字符迭代比较 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 语言核心库和标准库都对字符串内置了一些方便的方法来处理这类需求。 8 | 9 | 迭代字符的性能虽然也很快(对500多个字符迭代转义处理大概需要4.5微秒左右),但这种场景用迭代的话,代码可读性更差一些。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | let name = "_"; 15 | // 不符合 16 | name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); 17 | 18 | let name = "foo"; 19 | // 不符合 20 | if name.chars().next() == Some('_') {}; 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | let name = "_"; 27 | // 符合 28 | name.ends_with('_') || name.ends_with('-'); 29 | 30 | let name = "foo"; 31 | // 符合 32 | if name.starts_with('_') {}; 33 | ``` 34 | 35 | **【Lint 检测】** 36 | 37 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 38 | | -------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 39 | | [chars_last_cmp](https://rust-lang.github.io/rust-clippy/master/#chars_last_cmp) | yes | no | style | warn | 40 | | [chars_next_cmp](https://rust-lang.github.io/rust-clippy/master/#chars_next_cmp) | yes | no | style | warn | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/G.STR.05.md: -------------------------------------------------------------------------------- 1 | ## G.STR.05 对字符串按指定位置进行切片的时候需要小心破坏其 UTF-8 编码 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 字符串默认是合法的 `UTF-8`字节序列,如果通过指定索引位置来对字符串进行切片,有可能破坏其合法 `UTF-8` 编码,除非这个位置是确定的,比如按 `char_indices` 方法来定位是合法的。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::string_slice)] 13 | 14 | fn main(){ 15 | let s = "Ölkanne"; 16 | // 不符合 17 | // 字节索引 1 不是字符的边界,所以程序会 panic 18 | // `Ölkanne` 的 'Ö' 是 字节 `0..2` 19 | let sub_s = &s[1..]; 20 | // println!("{:?}", sub_s); 21 | } 22 | 23 | ``` 24 | 25 | **【正例】** 26 | 27 | ```rust 28 | #![allow(clippy::string_slice)] 29 | 30 | fn main(){ 31 | let s = "Ölkanne"; 32 | let mut char_indices = s.char_indices(); 33 | assert_eq!(Some((0, 'Ö')), char_indices.next()); 34 | // assert_eq!(Some((2, 'l')), char_indices.next()); 35 | let pos = if let Some((pos, _)) = char_indices.next(){ pos } else {0}; 36 | // 符合:计算出了正确的字符位置 37 | // 注意,这里 lint 检查工具可能误报,但这里是合法的,所以将lint设置为 allow 38 | let sub_s = &s[pos..]; 39 | assert_eq!("lkanne", sub_s); 40 | } 41 | ``` 42 | 43 | **【Lint 检测】** 44 | 45 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 46 | | ---------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 47 | | [string_slice](https://rust-lang.github.io/rust-clippy/master/#string_slice) | yes | no | restriction | allow | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/P.STR.01.md: -------------------------------------------------------------------------------- 1 | ## P.STR.01 处理字符串元素时优先按字节处理而非字符 2 | 3 | **【描述】** 4 | 5 | 处理字符串有两种方式,一种是按字符处理,即把字符串转为字符数组`[char]`,另一种是直接按字节处理`[u8]`。 6 | 7 | 两者之间的一些区别: 8 | 9 | - `[char]` 保证是有效的 Unicode,但不一定是有效的 UTF-8,一般将其看作是 UTF-32 。将字符数组转换为字符串需要注意。 10 | - `[u8]` 不一定是有效的字符串,它比 `[char]` 节省内存。将其转换为字符串需要检查 `UTF-8`编码。 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/P.STR.02.md: -------------------------------------------------------------------------------- 1 | ## P.STR.02 创建字符串时,宜预先分配大约足够的容量来避免后续操作中产生多次分配 2 | 3 | **【描述】** 4 | 5 | 预分配足够的容量,避免后续内存分配,可以提升代码性能。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | let mut output = String::new(); 11 | ``` 12 | 13 | **【正例】** 14 | 15 | ```rust 16 | let mut output = String::with_capacity(input.len()); 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/strings/P.STR.03.md: -------------------------------------------------------------------------------- 1 | ## P.STR.03 在使用内建字符串处理函数或方法的时候,应注意避免隐藏的嵌套迭代或多次迭代 2 | 3 | **【描述】** 4 | 5 | 比如 `contains` 函数的实现就是按字符遍历字符串,但是如果你将它用于一个字符串的迭代处理中,就会产生嵌套迭代,时间复杂度从你以为的 `O(n)` 变成了 `O(n^2)`。没有将其用于迭代中,也有可能产生多次迭代,`O(n)` 变为 `O(n+m)` 。 为了避免这个问题,我们可以用 `find` 来代替 `contains`。 6 | 7 | 所以,在使用内建函数的时候要注意它的实现,选择合适的函数或方法,来避免这类问题。 8 | 9 | **【示例】** 10 | 11 | ```rust 12 | // 对输入的字符串进行转义 13 | pub fn find<'a, S: Into>>(input: S) -> Cow<'a, str> { 14 | let input = input.into(); 15 | fn is_trouble(c: char) -> bool { 16 | c == '<' || c == '>' || c == '&' 17 | } 18 | 19 | // 使用 find 而非 contains 20 | // find 使用模式查找,可以返回匹配字符的位置信息 21 | let first = input.find(is_trouble); 22 | 23 | // 利用 find 的位置信息,避免第二次遍历 24 | if let Some(first) = first { 25 | let mut output = String::from(&input[0..first]); 26 | output.reserve(input.len() - first); 27 | let rest = input[first..].chars(); 28 | for c in rest { 29 | match c { 30 | '<' => output.push_str("<"), 31 | '>' => output.push_str(">"), 32 | '&' => output.push_str("&"), 33 | _ => output.push(c), 34 | } 35 | } 36 | 37 | Cow::Owned(output) 38 | } else { 39 | input.into() 40 | } 41 | } 42 | ``` 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads.md: -------------------------------------------------------------------------------- 1 | # 3.17 多线程 2 | 3 | Rust 天生可以有效消除数据竞争。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads/lock-free.md: -------------------------------------------------------------------------------- 1 | # 无锁并发 2 | 3 | Rust 也支持原子类型,其内存顺序模型与 `C++20` 相同。 4 | 5 | ## 列表 6 | 7 | - [P.MTH.LKF.01 除非必要,否则建议使用同步锁](./lock-free/P.MTH.LKF.01.md) 8 | - [P.MTH.LKF.02 使用无锁编程时,需要合理选择内存顺序](./lock-free/P.MTH.LKF.02.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads/lock-free/P.MTH.LKF.01.md: -------------------------------------------------------------------------------- 1 | ## P.MTH.LKF.01 除非必要,否则建议使用同步锁 2 | 3 | **【描述】** 4 | 5 | 无锁编程性能不一定比同步锁高。 6 | 7 | 使用无锁编程时需要注意的地方比使用同步锁多,比如指令重排、ABA 问题、内存顺序是否指定正确等。 8 | 正确实现无锁编程比使用同步锁要困难很多。所以,除非必要,否则直接使用同步锁就可以。 9 | 10 | 也有一些 [性能测试](https://github.com/magiclen/rust-performance-measurement/blob/master/benches/atomic_mutex.rs) 作为参考,原子类型的性能比互斥锁的性能大概要好四倍左右。所以,当在同一个临界区内要有超过四次原子操作,也许使用互斥锁更加简单一些。 11 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads/lock.md: -------------------------------------------------------------------------------- 1 | # 锁同步 2 | 3 | Rust 中多线程并发使用锁来进行线程同步。 4 | 5 | ## 列表 6 | 7 | - [P.MTH.LCK.01 多线程下要注意识别锁争用的情况,避免死锁](./lock/P.MTH.LCK.01.md) 8 | - [G.MTH.LCK.01 对布尔或引用并发访问应该使用原子类型而非互斥锁](./lock/G.MTH.LCK.01.md) 9 | - [G.MTH.LCK.02 建议使用 `Arc / Arc<[T]>` 来代替 `Arc / Arc>`](./lock/G.MTH.LCK.02.md) 10 | - [G.MTH.LCK.03 尽量避免直接使用标准库 `std::sync` 模块中的同步原语,替换为 `parking_lot`](./lock/G.MTH.LCK.03.md) 11 | - [G.MTH.LCK.04 尽量避免直接使用标准库 `std::sync::mpsc` 模块中的 `channel`,替换为 `crossbeam`](./lock/G.MTH.LCK.04.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads/lock/G.MTH.LCK.01.md: -------------------------------------------------------------------------------- 1 | ## G.MTH.LCK.01 对布尔或引用并发访问应该使用原子类型而非互斥锁 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 使用原子类型性能更好。但要注意指定合理的内存顺序。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 不符合 13 | let x = Mutex::new(&y); 14 | ``` 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | // 符合 20 | let x = AtomicBool::new(y); 21 | ``` 22 | 23 | **【Lint 检测】** 24 | 25 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 26 | | ---------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 27 | | [mutex_atomic](https://rust-lang.github.io/rust-clippy/master/#mutex_atomic) | yes | no | perf | warn | 28 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/threads/lock/G.MTH.LCK.04.md: -------------------------------------------------------------------------------- 1 | ## G.MTH.LCK.04 尽量使用[`crossbeam`](https://github.com/crossbeam-rs/crossbeam)模块的 `channel`,而不是`std::sync::mpsc::channel` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 尽量避免使用 `std::sync::mpsc::channel`,建议使用 [`crossbeam`](https://github.com/crossbeam-rs/crossbeam) 8 | 9 | **【反例】** 10 | 11 | 例子来源于 [`std::sync::mpsc` 文档](https://doc.rust-lang.org/std/sync/mpsc/) 12 | 13 | ```rust 14 | use std::thread; 15 | use std::sync::mpsc::channel; // 不符合 16 | 17 | let (tx, rx) = channel(); 18 | 19 | for i in 0..10 { 20 | let tx = tx.clone(); 21 | thread::spawn(move|| { 22 | tx.send(i).unwrap(); 23 | }); 24 | } 25 | 26 | for _ in 0..10 { 27 | let j = rx.recv().unwrap(); 28 | assert!(0 <= j && j < 10); 29 | } 30 | ``` 31 | 32 | **【正例】** 33 | 34 | ```rust 35 | use crossbeam_channel::unbounded; // 符合 36 | 37 | let (tx, rx) = unbounded(); 38 | 39 | for i in 0..10 { 40 | let tx = tx.clone(); 41 | thread::spawn(move|| { 42 | tx.send(i).unwrap(); 43 | }); 44 | } 45 | 46 | for _ in 0..10 { 47 | let j = rx.recv().unwrap(); 48 | assert!(0 <= j && j < 10); 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits.md: -------------------------------------------------------------------------------- 1 | # 3.11 特质 2 | 3 | 特质就是指 trait。在 Rust 中, trait 不是具体类型,而是一种抽象接口。但是通过 `impl Trait` 和 `dyn Trait` 也可以将 trait 作为类型使用。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/P.TRA.01.md: -------------------------------------------------------------------------------- 1 | ## P.TRA.01 使用 trait 时要注意 trait 一致性规则 2 | 3 | **【描述】** 4 | 5 | 使用 trait 的时候,必须要满足 trait 一致性规则,即,**孤儿规则(orphans rule)**:类型和trait,必须有一个是在本地crate内定义的。 6 | 当不满足孤儿规则时,可以考虑使用`NewType`模式来解决问题。 7 | 8 | **【正例】** 9 | ```rust 10 | // String 和 FromStr都在标准库中被定义 11 | // 如果想给String实现FromStr,则编译器会报错,告诉你这违反孤儿规则 12 | // (虽然标准库内已经为string实现了FromStr,这里只是示例) 13 | // 但是通过使用NewType,我们可以间接的达成目标 14 | // 使用这种单个元素的元组结构体包装一个类型就叫NewType模式。 15 | pub struct PhoneNumber(String); 16 | 17 | use std::str::FromStr; 18 | impl FromStr for PhoneNumber { 19 | type Err = Box; 20 | 21 | fn from_str(s: &str) -> Result { 22 | Ok(PhoneNumber(s.to_string())) 23 | } 24 | } 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin.md: -------------------------------------------------------------------------------- 1 | # 内置 trait 2 | 3 | Rust 标准库内置了很多 trait,在使用这些 trait 的时候也需要注意。 4 | 5 | ## 列表 6 | 7 | - [P.TRA.BLN.01 在实现`Borrow`特质时,需要注意一致性](./std-builtin/P.TRA.BLN.01.md) 8 | - [G.TRA.BLN.01 应该具体类型的 `default()` 方法代替 `Default::default()` 调用](./std-builtin/G.TRA.BLN.01.md) 9 | - [G.TRA.BLN.02 不要为迭代器实现Copy特质](./std-builtin/G.TRA.BLN.02.md) 10 | - [G.TRA.BLN.03 能使用派生宏(Derive)自动实现Default特质就不要用手工实现](./std-builtin/G.TRA.BLN.03.md) 11 | - [G.TRA.BLN.04 在使用`#[derive(Hash)]`的时候,避免再手工实现`PartialEq`](./std-builtin/G.TRA.BLN.04.md) 12 | - [G.TRA.BLN.05 在使用`#[derive(Ord)]` 的时候,避免再手工实现 `PartialOrd`](./std-builtin/G.TRA.BLN.05.md) 13 | - [G.TRA.BLN.06 不要对实现 `Copy` 或引用类型调用 `std::mem::drop` 和 `std::mem::forgot`](./std-builtin/G.TRA.BLN.06.md) 14 | - [G.TRA.BLN.07 对实现 `Copy` 的可迭代类型来说,要通过迭代器拷贝其所有元素时,应该使用 `copied`方法,而非`cloned`](./std-builtin/G.TRA.BLN.07.md) 15 | - [G.TRA.BLN.08 实现 `From` 而不是 `Into`](./std-builtin/G.TRA.BLN.08.md) 16 | - [G.TRA.BLN.09 一般情况下不要给 `Copy` 类型手工实现 `Clone`](./std-builtin/G.TRA.BLN.09.md) 17 | - [G.TRA.BLN.10 不要随便使用Deref特质来模拟继承](./std-builtin/G.TRA.BLN.10.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/G.TRA.BLN.01.md: -------------------------------------------------------------------------------- 1 | ## G.TRA.BLN.01 应该用具体类型的 `default()` 方法代替 `Default::default()` 调用 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 为了增强可读性。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::default_trait_access)] 13 | // 不符合 14 | let s: String = Default::default(); 15 | ``` 16 | 17 | **【正例】** 18 | 19 | ```rust 20 | #![warn(clippy::default_trait_access)] 21 | 22 | // 符合 23 | let s = String::default(); 24 | ``` 25 | 26 | **【Lint 检测】** 27 | 28 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 29 | | -------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 30 | | [default_trait_access](https://rust-lang.github.io/rust-clippy/master/#default_trait_access) | yes | no | pedantic | allow | 31 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/G.TRA.BLN.03.md: -------------------------------------------------------------------------------- 1 | ## G.TRA.BLN.03 能使用派生宏(Derive)自动实现`Default`特质就不要用手工实现 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 手工实现 Default,代码不精炼。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | struct Foo { 13 | bar: bool 14 | } 15 | // 不符合 16 | impl std::default::Default for Foo { 17 | fn default() -> Self { 18 | Self { 19 | bar: false 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | **【正例】** 26 | 27 | ```rust 28 | // 符合 29 | #[derive(Default)] 30 | struct Foo { 31 | bar: bool 32 | } 33 | ``` 34 | 35 | **【Lint 检测】** 36 | 37 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 38 | | ---------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 39 | | [derivable_impls](https://rust-lang.github.io/rust-clippy/master/#derivable_impls) | yes | no | complexity | warn | 40 | 41 | 该lint不能用于检测泛型参数类型的 Default 手工实现。 42 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/G.TRA.BLN.04.md: -------------------------------------------------------------------------------- 1 | ## G.TRA.BLN.04 在使用`#[derive(Hash)]` 的时候,避免再手工实现 `PartialEq` 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 实现 Hash 和 Eq 必须要满足下面一个等式: 8 | 9 | ```text 10 | k1 == k2 -> hash(k1) == hash(k2) 11 | ``` 12 | 13 | 即,当`k1` 和 `k2` 相等时,`hash(k1)`也应该和 `hash(k2)` 相等。 所以要求 `PartialEq` / `Eq` / `Hash` 的实现必须保持一致。 14 | 15 | 如果用 `#[derive(Hash)]` 的时候,搭配了一个手工实现的 `PartialEq` 就很可能出现不一致的情况。 16 | 17 | 但也有例外。 18 | 19 | **【反例】** 20 | 21 | ```rust 22 | #[derive(Hash)] 23 | struct Foo; 24 | // 不符合 25 | impl PartialEq for Foo { 26 | ... 27 | } 28 | ``` 29 | 30 | **【正例】** 31 | 32 | ```rust 33 | // 符合 34 | #[derive(PartialEq, Eq, Hash)] 35 | struct Foo; 36 | ``` 37 | 38 | **【例外】** 39 | 40 | ```rust 41 | // From: https://docs.rs/crate/blsttc/3.3.0/source/src/lib.rs 42 | 43 | // Clippy warns that it's dangerous to derive `PartialEq` and explicitly implement `Hash`, but the 44 | // `pairing::bls12_381` types don't implement `Hash`, so we can't derive it. 45 | #![allow(clippy::derive_hash_xor_eq)] 46 | ``` 47 | 48 | **【Lint 检测】** 49 | 50 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 51 | | ---------------------------------------------------------------------------------------- | ------------- | ------------ | ----------- | --------- | 52 | | [derive_hash_xor_eq](https://rust-lang.github.io/rust-clippy/master/#derive_hash_xor_eq) | yes | no | correctness | deny | 53 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/G.TRA.BLN.07.md: -------------------------------------------------------------------------------- 1 | ## G.TRA.BLN.07 对实现 `Copy` 的可迭代类型来说,要通过迭代器拷贝其所有元素时,应该使用 `copied`方法,而非`cloned` 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | `copied` 方法在语义层面,是针对实现 `Copy` 的类型,所以应该使用 `copied` 来增加代码可读性。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::cloned_instead_of_copied)] 13 | 14 | let a = [1, 2, 3]; 15 | // 不符合 16 | let v_copied: Vec<_> = a.iter().cloned().collect(); 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | #![warn(clippy::cloned_instead_of_copied)] 23 | let a = [1, 2, 3]; 24 | // 符合 25 | let v_copied: Vec<_> = a.iter().copied().collect(); 26 | ``` 27 | 28 | **【Lint 检测】** 29 | 30 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认level | 31 | | ---------------------------------------------------------------------------------------------------- | ------------- | ------------ | ---------- | --------- | 32 | | [cloned_instead_of_copied](https://rust-lang.github.io/rust-clippy/master/#cloned_instead_of_copied) | yes | no | pedantic | allow | 33 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/G.TRA.BLN.10.md: -------------------------------------------------------------------------------- 1 | ## G.TRA.BLN.10 不要随便使用`Deref`特质来模拟继承 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | `Deref` trait是专门用于实现自定义指针类型而存在的。虽然可以实现 `Deref` 来达到某种类似于继承的行为,但 Rust 中不推荐这样做。 8 | 9 | 这是因为 Rust 语言推崇显式的转换,而 `Deref` 则是 Rust 中为数不多的隐式行为。如果 `Deref` 被滥用,那么程序中隐式行为可能会增多,隐式的转换是 Bug 的温床。 10 | 11 | **【反例】** 12 | 不要像下面这样用`Deref`来模拟继承。 13 | ```rust 14 | use std::ops::Deref; 15 | 16 | struct Foo {} 17 | 18 | impl Foo { 19 | fn m(&self) { 20 | // ... 21 | } 22 | } 23 | 24 | struct Bar { 25 | f: Foo 26 | } 27 | 28 | impl Deref for Bar { 29 | type Target = Foo; 30 | 31 | fn deref(&self) -> &Foo { 32 | &self.f 33 | } 34 | } 35 | 36 | fn main() { 37 | let bar = Bar { f: Foo {} }; 38 | bar.m(); 39 | } 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/std-builtin/P.TRA.BLN.01.md: -------------------------------------------------------------------------------- 1 | ## P.TRA.BLN.01 在实现`Borrow`特质时,需要注意一致性 2 | 3 | **【描述】** 4 | 5 | 当你想把不同类型的借用进行统一抽象,或者当你要建立一个数据结构,以同等方式处理自拥有值(ownered)和借用值(borrowed)时,例如散列(hash)和比较(compare)时,选择`Borrow`。当把某个类型直接转换为引用,选择 `AsRef` 。 6 | 7 | 但是使用 `Borrow` 的时候,需要注意一致性问题。具体请看示例。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // 这个结构体能不能作为 HashMap 的 key? 13 | pub struct CaseInsensitiveString(String); 14 | 15 | // 它实现 Eq 没有问题 16 | impl PartialEq for CaseInsensitiveString { 17 | fn eq(&self, other: &Self) -> bool { 18 | // 但这里比较是要求忽略了 ascii 大小写 19 | self.0.eq_ignore_ascii_case(&other.0) 20 | } 21 | } 22 | 23 | impl Eq for CaseInsensitiveString { } 24 | 25 | // 实现 Hash 没有问题 26 | // 但因为 eq 忽略大小写,那么 hash 计算也必须忽略大小写 27 | impl Hash for CaseInsensitiveString { 28 | fn hash(&self, state: &mut H) { 29 | for c in self.0.as_bytes() { 30 | // 不符合:没有忽略大小写 31 | c.to_ascii_lowercase().hash(state) 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | 这种情况下,就不能为 `CaseInsensitiveString` 实现 `Borrow`,并非编译不通过,而是在逻辑上不应该为其实现 `Borrow`,因为 `CaseInsensitiveString` 实现 `Eq` 和 `Hash` 的行为不一致,而 `HashMap` 则要求 `Key` 必须 `Hash` 和 `Eq` 的实现一致。这种不一致,编译器无法检查,所以在逻辑上,就不应该为其实现 `Borrow`。如果强行实现,那可能会出现逻辑 Bug。 38 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/traits/trait-object.md: -------------------------------------------------------------------------------- 1 | # trait 对象 2 | 3 | trait 对象需要注意 动态安全 (dyn safe),也叫对象安全 (object safe),但官方现在倾向于 动态安全这个术语。 4 | 5 | ## 列表 6 | 7 | - [P.TRA.OBJ.01 根据场景合理选择使用trait对象或泛型静态分发](./trait-object/P.TRA.OBJ.01.md) 8 | - [P.TRA.OBJ.02 除非必要,避免自定义虚表](./trait-object/P.TRA.OBJ.02.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust.md: -------------------------------------------------------------------------------- 1 | # 3.19 Unsafe Rust 2 | 3 | Unsafe Rust 是 Safe Rust 的超集,意味着在 Unsafe Rust 中也会有 Safe Rust 的安全检查。但是 Unsafe Rust 中下面五件事是Safe Rust 的检查鞭长莫及的地方: 4 | 5 | 1. 解引用裸指针 6 | 2. 调用 `unsafe`函数(C函数,编译器内部函数或原始分配器) 7 | 3. 实现 `unsafe` trait 8 | 4. 可变静态变量 9 | 5. 访问 `union` 的字段 10 | 11 | 使用 Unsafe Rust 的时候,需要遵守一定的规范,这样可以避免未定义行为的发生。 12 | 13 | 关于 Unsafe Rust 下的一些专用术语可以查看 [Unsafe 代码术语指南](./unsafe_rust/glossary.md) 。 14 | 15 | **Unsafe Rust 的语义:这是编译器无法保证安全的地方,需要程序员来保证安全。** 16 | 17 | [Unsafe 代码术语指南](./unsafe_rust/glossary.md) -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/G.UNS.01.md: -------------------------------------------------------------------------------- 1 | ## G.UNS.01 不宜为带有 `unsafe` 命名的类型或方法创建别名 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 里 `unsafe` 字样用于提醒开发者在编写代码的时候注意保证安全。如果修改别名,隐藏了这种提醒,不利于展示这种信息。 8 | 9 | 不利于开发者去保证安全。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | use std::cell::{UnsafeCell as TotallySafeCell}; 15 | 16 | extern crate crossbeam; 17 | use crossbeam::{spawn_unsafe as spawn}; 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | use std::cell::{UnsafeCell}; 24 | 25 | extern crate crossbeam; 26 | use crossbeam::{spawn_unsafe}; 27 | ``` 28 | 29 | **【Lint 检测】** 30 | 31 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 32 | | ------------------------------------------------------------ | ------------- | ------------ | ---------- | ----- | 33 | | [unsafe_removed_from_name](https://rust-lang.github.io/rust-clippy/master/#unsafe_removed_from_name) | yes | no | style | warn | 34 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/P.UNS.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.01 不要为了逃避编译器安全检查而滥用 Unsafe Rust 2 | 3 | **【描述】** 4 | 5 | Unsafe Rust 有其应用范围和目标,不要为了逃避 编译器安全检查而随便滥用 Unsafe Rust,否则很可能引起未定义行为(UB)。 6 | 7 | 【反例】 8 | 9 | ```rust 10 | // 该函数为滥用 unsafe 来跳过 Rust 借用检查 11 | // 强行返回本地变量的引用,最终引起 UB 未定义行为 12 | fn abuse_unsafe_return_local_ref<'a>() -> &'a String { 13 | let s = "hello".to_string(); 14 | let ptr_s_addr = &s as *const String as usize; 15 | unsafe{ &*(ptr_s_addr as *const String) } 16 | } 17 | 18 | fn main() { 19 | let s = abuse_unsafe_return_local_ref(); // error: Undefined Behavior: encountered a dangling reference (use-after-free) 20 | } 21 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/P.UNS.02.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.02 不要为了提升性能而盲目使用 Unsafe Rust 2 | 3 | **【描述】** 4 | 5 | 对比 Safe 代码的性能看是否够用,就可以减少不必要的 Unsafe。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/P.UNS.03.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.03 要精确使用 `unsafe` 块的范围 2 | 3 | **【描述】** 4 | 5 | 比如只有对调用 C 函数或其他 `unsafe` 函数时才使用 `unsafe` 块,而不要将多余的代码都包入 `unsafe` 块中。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | pub fn io_read_u32(ioaddr: usize) -> Result { 11 | // ... 12 | unsafe { 13 | let val = ptr::read_volatile(ioaddr); 14 | trace!("io_read_u32 {:#x}={:#x}", ioaddr, val); 15 | Ok(val) 16 | } 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | pub fn io_read_u32(ioaddr: usize) -> Result { 24 | // ... 25 | let val = unsafe { ptr::read_volatile(ioaddr) }; 26 | trace!("io_read_u32 {:#x}={:#x}", ioaddr, val); 27 | Ok(val) 28 | } 29 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi.md: -------------------------------------------------------------------------------- 1 | # FFi 规范 2 | 3 | Rust 可以通过C-ABI无缝与C语言打交道,也可以通过暴露 C-ABI 接口供其他语言调用。但是跨边界本质上是不安全的。 4 | 5 | 一般来说,FFi 是指在其他语言中调用 Rust 代码,Rust代码会按 C-ABI 来暴露接口。这类 Rust crate或模块,常以 `-ffi`后缀结尾。 6 | 7 | 另一类是 Rust 去调用 C-ABI 接口,相关代码通常被封装到以 `-sys` 为后缀命名的 crate 或 模块中。 8 | 9 | 本小节内容,包含以上两种情况。 10 | 11 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.01 避免从公开的 Rust API 直接传字符串到 C 中 2 | 3 | **【描述】** 4 | 5 | 在跨越 C 边界的时候,应该对 字符串进行边界检查,避免传入一些非法字符串。 6 | 7 | **【正例】** 8 | 9 | 这个示例中,从公开的 `Rust API` 传入非法字符串到 `C`,导致字符串格式化漏洞。 10 | 11 | ```rust 12 | // From: https://github.com/RustSec/advisory-db/issues/106 13 | 14 | extern crate pancurses; 15 | 16 | use pancurses::{initscr, endwin}; 17 | 18 | fn main() { 19 | let crash = "!~&@%+ S"; // 特意构造非法字符串 20 | 21 | let window = initscr(); 22 | window.printw(crash); // 通过该函数跨 C 边界传入非法字符串,引起字符串格式化漏洞 23 | window.refresh(); 24 | window.getch(); 25 | endwin(); 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.02.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.02 在使用标准库 `std::ffi` 模块提供的类型时需要仔细查看其文档 2 | 3 | **【描述】** 4 | 5 | 因为该模块中提供了用于和其他语言类 C 字符串打交道的 FFi 绑定和类型,在使用前务必要看清楚它们的文档,否则会因为所有权管理不当而导致无效内存访问、内存泄漏和其他内存错误。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.03.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.03 当使用来自 C 的指针时,如果该指针需要管理内存,则需要为包装该指针的 Rust 类型实现 Drop 特质 2 | 3 | **【描述】** 4 | 5 | Rust 里通过结构体包装该指针,并且为该结构体实现 Drop 来保证相关资源可以安全释放。 6 | 7 | **【正例】** 8 | 9 | 下面示例中 `*mut sys::VMContext` 是来自于外部的 `C-ABI` 指针,它需要管理内存,所以在 Rust 这边使用结构体包装该指针,并实现 `Drop`,通过 `Drop` 来调用 `C-ABI` 回调函数来释放内存。 10 | 11 | ```rust 12 | pub struct Vm { 13 | pub(crate) ctx: *mut sys::VMContext, 14 | } 15 | 16 | impl Drop for Vm { 17 | fn drop(&mut self) { 18 | if !self.ctx.is_null() { 19 | unsafe { sys::VMDelete(self.ctx) }; 20 | } 21 | } 22 | } 23 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.04.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.04 如果一个函数正在跨越 FFi 边界,那么需要处理 Panic 2 | 3 | **【描述】** 4 | 5 | 如果让 Panic 在跨越 FFi 边界时发生,可能会产生未定义行为。 6 | 7 | 处理 Panic 可以使用 `catch_unwind`,但是它只对实现了 `UnwindSafe` trait 的类型起作用。另外一种方法就是避免 Panic,而返回错误码。 8 | 9 | **【正例】** 10 | 11 | ```rust 12 | use std::panic::catch_unwind; 13 | 14 | #[no_mangle] 15 | pub extern fn oh_no() -> i32 { 16 | let result = catch_unwind(|| { 17 | panic!("Oops!"); // 这里会发生 Panic,需要处理 18 | }); 19 | match result { 20 | Ok(_) => 0, 21 | Err(_) => 1, 22 | } 23 | } 24 | 25 | fn main() {} 26 | ``` 27 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.05.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.05 建议使用诸如标准库或 `libc` crate 所提供的可移植类型别名,而不是特定平台的类型 2 | 3 | **【描述】** 4 | 5 | 当与外部(如 C 或 C++)接口交互时,通常需要使用平台相关的类型,如 C 的 `int`、`long` 等。除了 `std::ffi` (或 `core::ffi` )中的 c void 外,标准库还在 `std:os::raw` (或 `core::os::raw` )中提供了可移植类型别名。`libc` crate 基本覆盖了所有的 C 标准库中的 C 兼容类型。 6 | 7 | 这样有助于编写跨平台的代码。 8 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.06.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.06 Rust 和 C 之间传递字符或字符串时需要注意字符串要符合 C-ABI 以及 字符串的编码 2 | 3 | **【描述】** 4 | 5 | 注意要使用 `c_char` 对应 C 语言的字符。`libc::c_char` 和 `std::os::raw::c_char` 在大多数 64位 linux 上都是相同的。 6 | 7 | FFi 接口使用的字符串要符合 C 语言约定,即使用 `\0` 结尾且中间不要包含 `\0`字符的字符串。 8 | 9 | Rust 中字符串要求 `utf-8` 编码,而 C 字符串则没有这个要求。所以需要注意编码。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | let f = libc::fopen("/proc/uptime".as_ptr().cast(), "r".as_ptr().cast()); 15 | // 即使 /proc/uptime 文件存在,fopen 系统调用也会返回 NULL 16 | // 并且将错误码 errno 标记为 2 ("No such file or directory") 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | let f = libc::fopen("/proc/uptime\0".as_ptr().cast(), "r\0".as_ptr().cast()); 23 | ``` 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.07.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.07 不要为任何传出外部的类型实现 Drop 2 | 3 | **【描述】** 4 | 5 | 因为有可能在传出去之前被析构。需要明确是由哪种语言负责分配和释放内存,谁分配内存,谁来释放。 6 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.09.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.09 当 Rust 调用外部 C 函数时,如果可以确认安全,可以通过引用来代替裸指针 2 | 3 | **【描述】** 4 | 5 | 在确认安全的前提下,在声明外部 C 函数时可以直接使用引用形式, C 语言可以使用正确绑定。 -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.10.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.10 当 Rust 函数导出外部函数时,必须从设计上保证被跨线程调用的安全性 2 | 3 | **【描述】** 4 | 5 | 当 Rust 函数被导出为外部函数接口时,要保证其被跨线程调用的安全性。除非调用它的环境是单线程。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | #[no_mangle] 11 | pub extern "C" fn nic_udrv_suspend() { 12 | NIC_ENTITY.try_borrow_mut().suspend(); // suspend()需要可变引用 13 | } 14 | 15 | // 对外被 C 调用的接口 16 | #[no_mangle] 17 | pub extern "C" fn nic_udrv_buf_recycle(buf_id: usize) { 18 | NIC_ENTITY.try_borrow().buf_recycle(buf_id); // buf_recycle()内有锁可以避免多线程竞争 19 | } 20 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.13.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.13 自定义数据类型要保证一致的数据布局 2 | 3 | **【描述】** 4 | 5 | Rust 编译器为了优化内存布局,会对结构体字段进行重排。所以在 FFi 边界,应该注意结构体内存布局和 C 语言的一致。 6 | 7 | 关于 如何选择合适的 `repr` 属性可参考:[P.UNS.MEM.01](../mem.md) 8 | 9 | 以下是不适合用于和 C 语言交互的类型: 10 | 11 | 1. 没有使用任何 `#[repr( )]` 属性修饰的自定义类型 12 | 2. 动态大小类型 (dynamic sized type) 13 | 3. 指向动态大小类型对象的指针或引用 (fat pointers) 14 | 4. str 类型、tuple 元组、闭包类型 15 | 16 | **【正例】** 17 | 18 | ```rust 19 | #[repr(C)] 20 | struct Data { 21 | a: u32, 22 | b: u16, 23 | c: u64, 24 | } 25 | #[repr(C, packed)] 26 | struct PackedData { 27 | a: u32, 28 | b: u16, 29 | c: u64, 30 | } 31 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.14.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.14 在 FFi 中使用的类型应该拥有稳定布局 2 | 3 | **【描述】** 4 | 5 | **FFi-Safe**: 通过 FFi 外部传递的结构体类型都要满足内存布局的稳定性。 6 | 7 | 为结构体添加 `#[repr(C)]` 或 `#[repr(transparent)]` 可以让结构体拥有稳定的布局。 8 | 9 | 零大小类型在 C 中是无效的。也不要把 Rust 中的单元类型 `()` 和 C 中的 `void` 混为一谈。所以不应该在 FFi 中使用零大小类型(ZST)。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | // Foo 为零大小类型 15 | // No FFi Safe 16 | #[repr(C)] 17 | pub struct Foo; 18 | 19 | extern { 20 | fn get_some_instance() -> *mut Foo; 21 | } 22 | ``` 23 | 24 | **【正例】** 25 | 26 | ```rust 27 | // 如果 C 函数需要 opaque 类型,可以使用 libc::c_void 解决 28 | extern crate libc; 29 | 30 | extern "C" { 31 | pub fn foo(arg: *mut libc::c_void); 32 | pub fn bar(arg: *mut libc::c_void); 33 | } 34 | 35 | // 如果一定要使用零大小类型,比如 C 函数中返回一个结构体指针 36 | // 可以按下面这种方式 37 | #[repr(C)] 38 | pub struct Foo { _unused: [u8; 0]} 39 | 40 | // 理论上上面结构体应该是下面空枚举的一种等价模拟,因为现在 Rust 编译器还不支持给空枚举设置布局 41 | // #[repr(C)] 42 | pub enmu Foo{}; 43 | 44 | extern { 45 | fn get_some_instance() -> *mut Foo; 46 | } 47 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.15.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FFI.15 从外部传入的不健壮类型的外部值要进行检查 2 | 3 | **【描述】** 4 | 5 | Safe Rust 会保证类型的有效性和安全性,但是 Unsafe Rust 中,特别是编写 FFi 的时候,很容易从外部传入无效值。 6 | 7 | Rust 中很多类型都不太健壮: 8 | 9 | - 布尔类型。外部传入的布尔类型可能是数字也可能是字符串。 10 | - 引用类型。Rust 中的引用仅允许执行有效的内存对象,但是在Unsafe 中使用引用,任何偏差都可能引起未定义行为。 11 | - 函数指针。跨越 FFi 边界的函数指针可能导致任意代码执行。 12 | - Enum。 跨 FFi 边界两端的 枚举值要经过合法转换。 13 | - 浮点数。 14 | - 包含上述类型的复合类型 15 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.17.md: -------------------------------------------------------------------------------- 1 | # P.UNS.FFI.17 当Rust绑定C-API不透明(Opaque)类型时,应该使用指向专用不透明类型的指针而不是`c_void`指针 2 | 3 | **【描述】** 4 | 5 | 使用专门构建的不透明类型相比于直接使用 `c_void`可以提供一定程度的类型安全性。 6 | 7 | **【正例】** 8 | 9 | C 库中包含了一个不透明类型的 foo 指针和 bar 指针: 10 | 11 | ```c 12 | void foo(void *arg); 13 | void bar(void *arg); 14 | ``` 15 | 16 | 通过包含私有字段`_private`且不包含构造函数,创建了两个无法在此模块之外实例化的不透明类型。空数组既是零大小又可设置布局为`#[repr(C)]`。 17 | 18 | ```rust 19 | #[repr(C)] 20 | pub struct Foo {_private: [u8; 0]} 21 | 22 | #[repr(C)] 23 | pub struct Bar {_private: [u8; 0]} 24 | 25 | 26 | // SAFETY: 27 | // 因为 Foo 和 Bar类型不同,所以将在它们两者之间获得类型安全,这样就不可能意外地传递一个指向 `bar()` 的`Foo`指针。 28 | extern "C" { 29 | fn foo(arg: *mut Foo); 30 | fn bar(arg: *mut Bar); 31 | } 32 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/ffi/P.UNS.FFI.18.md: -------------------------------------------------------------------------------- 1 | # P.UNS.FFI.18 避免将 trait 对象传递给 C 接口 2 | 3 | **【描述】** 4 | 5 | Rust 中的多态性主要由 trait 来提供。但是在 FFI 时,将 Rust trait 对象传递给 C 接口,并不能保证 FFI 安全。因为 Rust trait 对象没有稳定的 ABI,所以我们不能通过 `Box` 值传递越过 FFI 边界。 6 | 7 | 所以,最好的方式是不要在 FFI 时通过传递 trait对象来使用多态性。 8 | 9 | > 如果必须要在 FFI 中使用多态性,有以下几种方式: 10 | > 1. 使用枚举。像 C 传递一个指向枚举的指针。 11 | > 2. 使用 [`thin_trait_object`](https://github.com/kotauskas/thin_trait_object) 模式,是 FFI 安全的。 12 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/io.md: -------------------------------------------------------------------------------- 1 | # Unsafe I/O 2 | 3 | Rust 标准库提供了 I/O 安全性,保证程序持有私有的原始句柄(raw handle),其他部分无法访问它。但是 `FromRawFd::from_raw_fd` 是 Unsafe 的,所以在 Safe Rust中无法做到 `File::from_raw(7)` 这种事。 在这个文件描述符上面进行` I/O` 操作,而这个文件描述符可能被程序的其他部分私自持有。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/io/P.UNS.FIO.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.FIO.01 在使用原始句柄的时候,要注意 I/O 安全性 2 | 3 | **【描述】** 4 | 5 | 很多 API 通过接受原始句柄来进行 I/O 操作: 6 | 7 | ```rust 8 | pub fn do_some_io(input: &FD) -> io::Result<()> { 9 | some_syscall(input.as_raw_fd()) 10 | } 11 | ``` 12 | 13 | `AsRawFd`并没有限制`as_raw_fd`的返回值,所以`do_some_io`最终可以在任意的`RawFd`值上进行 `I/O `操作。甚至可以写`do_some_io(&7)`,因为`RawFd`本身实现了`AsRawFd`。这可能会导致程序访问错误的资源。甚至通过创建在其他部分私有的句柄别名来打破封装边界,导致一些诡异的 远隔作用(Action at a distance)。 14 | 15 | > **远隔作用**(**Action at a distance**)是一种程式设计中的[反模式](https://zh.wikipedia.org/wiki/反模式),是指程式某一部分的行为会广泛的受到程式其他部分[指令](https://zh.wikipedia.org/wiki/指令)的影响,而且要找到影响其他程式的指令很困难,甚至根本无法进行。 16 | 17 | 在一些特殊的情况下,违反 I/O 安全甚至会导致内存安全。 18 | 19 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/mem.md: -------------------------------------------------------------------------------- 1 | # 内存 2 | 3 | 这里指 Unsafe Rust 下的数据布局、内存管理和使用相关规范。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/mem/P.UNS.MEM.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.MEM.01 要注意选择合适的结构体、元组、枚举的数据布局 2 | 3 | **【描述】** 4 | 5 | 对于 Rust 中结构体和元组,编译器会随意重排其字段来优化布局。请根据具体的场景来选择合适的数据布局。 6 | 7 | 可以通过以下 `#[repr]` 属性来控制结构体和元组的数据布局: 8 | 9 | - `#[repr(Rust)]` ,默认 Rust 数据布局 10 | - `#[repr(C)]` ,与 C 兼容的布局 11 | - `#[repr(align(N))]` ,指定对齐方式 12 | - `#[repr(packed)]` ,指定字段将不在内部对齐 13 | - `#[repr(transparent)]` ,让包含单个字段的结构体布局和其字段相同 14 | 15 | 可以通过以下 `#[repr]` 属性来控制枚举体的数据布局: 16 | 17 | - 特定整数类型 18 | - `#[repr(u8)]` 19 | - `#[repr(u16)]` 20 | - `#[repr(u32)]` 21 | - `#[repr(u64)]` 22 | - `#[repr(i8)]` 23 | - `#[repr(i16)]` 24 | - `#[repr(i32)]` 25 | - `#[repr(i64)]` 26 | - C 兼容布局 27 | - `#[repr(C)]` 28 | - 指定判别式大小的 C 兼容布局 29 | - `#[repr(C, u8)]` 30 | - `#[repr(C, u16)]` 31 | - 以此类推 32 | 33 | 枚举需要注意的地方: 34 | 35 | - 枚举不允许通过 `#[repr(align)]` 手动指定对齐方式。 36 | - 空枚举不能使用 `repr` 属性 37 | - 无字段枚举不允许指定判别式大小的 C 兼容布局,比如 `[repr(C, Int)]` 38 | - 数据承载(有字段)枚举则允许所有类型的 `repr` 属性 39 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/mem/P.UNS.MEM.02.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.MEM.02 不能修改其它进程或动态库的内存变量 2 | 3 | **【描述】** 4 | 5 | 除非调用合法的API,否则不要尝试修改其它进程/动态库的内存数据,否则会出现内存段错误(SIGSEGV)。 6 | 7 | **【反例】** 8 | 9 | `sqlite3_libversion()` 返回的 sqlite 版本信息指针指向 `/usr/lib/libsqlite3.so` 动态库的 static 字符串。 10 | 11 | libsqlite3.so 中分配的静态字符串不属于进程的内存范围中。 12 | 13 | 当进程尝试修改 sqlite 动态库的静态字符串内容,操作系统就会发送 SIGSEGV 信号终止进程,以保证 sqlite 动态库的内存数据安全。 14 | 15 | ```rust 16 | #[link(name = "sqlite3")] 17 | extern "C" { 18 | fn sqlite3_libversion() -> *mut std::os::raw::c_char; 19 | } 20 | 21 | fn edit_sqlite_version() { 22 | unsafe { 23 | let mut sqlite_version = sqlite3_libversion(); 24 | // SIGSEGV: invalid memory reference 25 | *sqlite_version = 3; 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/mem/P.UNS.MEM.03.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.MEM.03 不能让 `String/Vec` 自动 Drop 其它进程或动态库的内存数据 2 | 3 | **【描述】** 4 | 5 | 使用 `String/Vec` 指向其它进程/动态库的内存数据时,一定要手动禁止 `String/Vec` 的 Drop 方法(析构函数)的调用,避免 free 其它进程/动态库的内存数据。 6 | 7 | **【反例】** 8 | 9 | `sqlite3_libversion()` 返回的 sqlite 版本信息指针指向 `/usr/lib/libsqlite3.so` 动态库的 static 字符串。 10 | 11 | 当进程在 `String` drop 的时候尝试释放 sqlite 动态库的静态字符串内存时,操作系统就会发送 SIGABRT 信号终止进程,以保证 sqlite 动态库的内存数据安全。 12 | 13 | ```rust 14 | #[link(name = "sqlite3")] 15 | extern "C" { 16 | fn sqlite3_libversion() -> *mut std::os::raw::c_char; 17 | } 18 | 19 | fn print_sqlite_version() { 20 | unsafe { 21 | let ptr = sqlite3_libversion(); 22 | let len = libc::strlen(ptr); 23 | let version = String::from_raw_parts(ptr.cast(), len, len); 24 | println!("found sqlite3 version={}", version); 25 | // SIGABRT: invalid free 26 | } 27 | } 28 | ``` 29 | 30 | **【正例】** 31 | 32 | 除了用 `mem::forget` 或者 `ManualDrop` 禁止 `String` drop 其它动态库的内存,也可以用标准库 `ptr/slice` 的 `copy` 或者 `libc::strdup` 将 sqlite 的版本信息字符串**复制到当前进程的内存空间**再进行操作 33 | 34 | ```rust 35 | fn print_sqlite_version() { 36 | unsafe { 37 | let ptr = sqlite3_libversion(); 38 | let len = libc::strlen(ptr); 39 | let version = String::from_raw_parts(ptr.cast(), len, len); 40 | println!("found sqlite3 version={}", version); 41 | // 手动禁止 String 的析构函数调用 42 | std::mem::forget(version); 43 | } 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/mem/P.UNS.MEM.04.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.MEM.04 尽量用可重入(reentrant)版本的 C-API 或系统调用 2 | 3 | **【描述】** 4 | 5 | 以 Linux 系统为例,在 **glibc**(/usr/lib/libc.so) 等知名 C 语言库中,很多 API 会既提供不可重入版本和**可重入(reentrant)**版本,例如 ctime 和 ctime_r 这对系统调用。可重入版本的函数命名一般带 **_r** 的后缀,*_r* 也就是单词可重入 reentrant 的缩写。 6 | 7 | > libc 中不可重入函数的执行过程一般是将函数的输出写到动态库的某个 static 命令内,然后再返回指向该 static 变量的指针返回给调用方,因此是一种「有状态」的函数,多线程环境下可能有**线程安全问题**。 8 | 9 | 使用不可重入函数的风险会导致开发人员带来很大的心智负担,需要耗费人力进行代码安全评审确保没有线程安全和内存安全问题,因此必须尽量使用可重入版本的函数。 10 | 11 | **【反例】** 12 | 13 | `ctime`, `gmtime`,` localtime`, `gethostbyname` 14 | 15 | **【正例】** 16 | 17 | `chrono` 库中用 `libc::localtime_r` 获取本地时间而不用 `libc::localtime`。 18 | 19 | 还有诸如 `ctime_r`, `gmtime_r`,` localtime_r`, `gethostbyname_r`等。 20 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/raw_ptr.md: -------------------------------------------------------------------------------- 1 | # 裸指针操作 2 | 3 | Rust提供了`*const T`(不变)和`*mut T`(可变)两种指针类型。因为这两种指针和C语言中的指针十分相近,所以叫其原生指针(Raw Pointer)。 4 | 5 | 原生指针具有以下特点: 6 | 7 | - 并不保证指向合法的内存。比如很可能是一个空指针。 8 | - 不能像智能指针那样自动清理内存。需要像 C 语言那样手动管理内存。 9 | - 没有生命周期的概念,也就是说,编译器不会对其提供借用检查。 10 | - 不能保证线程安全。 11 | 12 | 可见,原生指针并不受Safe Rust提供的那一层“安全外衣”保护,所以也被称为“裸指针”。 13 | 14 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/raw_ptr/G.UNS.PTR.03.md: -------------------------------------------------------------------------------- 1 | ## G.UNS.PTR.03 尽量使用 `pointer::cast` 来代替 使用 `as` 强转指针 2 | 3 | **【级别】** 要求 4 | 5 | **【描述】** 6 | 7 | 使用 `pointer::cast` 方法转换更加安全,它不会意外地改变指针的可变性,也不会将指针转换为其他类型。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | let ptr: *const u32 = &42_u32; 13 | let mut_ptr: *mut u32 = &mut 42_u32; 14 | let _ = ptr as *const i32; // 不符合 15 | let _ = mut_ptr as *mut i32; // 不符合 16 | ``` 17 | 18 | **【正例】** 19 | 20 | ```rust 21 | let ptr: *const u32 = &42_u32; 22 | let mut_ptr: *mut u32 = &mut 42_u32; 23 | let _ = ptr.cast::(); // 符合 24 | let _ = mut_ptr.cast::(); // 符合 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | 默认 level | 30 | | ------------------------------------------------------------------------ | ------------- | ------------ | ----------- | ---------- | 31 | | [ptr_as_ptr](https://rust-lang.github.io/rust-clippy/master/#ptr_as_ptr) | yes | no | correctness | deny | 32 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/raw_ptr/P.UNS.PTR.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.PTR.01 不要将裸指针在多线程间共享 2 | 3 | **【描述】** 4 | 5 | 裸指针在 Rust 中不是线程安全的,将裸指针在多线程传递编译器也会编译出错。如果需要在多线程间共享裸指针,则考虑使用 `NewType` 模式来包装它。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | struct MyBox(*mut u8); 11 | 12 | unsafe impl Send for MyBox {} 13 | unsafe impl Sync for MyBox {} 14 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/raw_ptr/P.UNS.PTR.02.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.PTR.02 建议使用 `NonNull` 来替代 `*mut T` 2 | 3 | **【描述】** 4 | 5 | 尽量使用 [`NonNull`](https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html) 来包装 `*mut T`。 6 | 7 | `NonNull` 的优势: 8 | 9 | 1. 非空指针。会自动检查包装的指针是否为空。 10 | 2. 协变。方便安全抽象。如果用裸指针,则需要配合 `PhantomData`类型来保证协变。 11 | 12 | **【正例】** 13 | 14 | ```rust 15 | use std::ptr::NonNull; 16 | 17 | let mut x = 0u32; 18 | let ptr = NonNull::::new(&mut x as *mut _).expect("ptr is null!"); 19 | 20 | if let Some(ptr) = NonNull::::new(std::ptr::null_mut()) { 21 | unreachable!(); 22 | } 23 | ``` 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/raw_ptr/P.UNS.PTR.03.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.PTR.03 使用指针类型构造泛型结构体时,需要使用 `PhantomData` 来指定 `T`上的协变和所有权 2 | 3 | **【描述】** 4 | 5 | `PhantomData` 是经常被用于 Unsafe Rust 中配合裸指针来指定协变和所有权的,为裸指针构建的类型保证安全性和有效性。否则,可能会产生未定义行为。 6 | 7 | 参考: [`PhantomData` 的型变(variance)模式表](https://doc.rust-lang.org/nomicon/phantom-data.html) 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | // Vec 不拥有类型 T,并且 data 字段的裸指针不支持协变 13 | // 这样的话,是有风险的。 14 | // 为 Vec 实现的 Drop 可能导致 UB 15 | struct Vec { 16 | data: *const T, 17 | len: usize, 18 | cap: usize, 19 | } 20 | ``` 21 | 22 | **【正例】** 23 | 24 | ```rust 25 | use std::marker; 26 | 27 | struct Vec { 28 | data: *const T, // *const for variance! 29 | len: usize, 30 | cap: usize, 31 | _marker: marker::PhantomData, // 让 Vec 拥有 T,并且让 指针有了协变 32 | } 33 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/safe_abstract.md: -------------------------------------------------------------------------------- 1 | # 安全抽象规范 2 | 3 | 使用 Unsafe Rust 的一种方式是将 Unsafe 的方法或函数进行安全抽象,将其变成安全的方法或函数。 4 | 5 | Unsafe Rust 中 API 的安全性设计通常有两种方式: 6 | 7 | 1. 将内部的 unsafe API 直接暴露给 API 的使用者,并且使用 `unsafe` 关键字来声明该 API 是非安全的,同时也需要对安全边界条件添加注释。 8 | 2. 对 API 进行安全封装,即,安全抽象。在内部使用断言来保证当越过安全边界时可以 Panic,从而避免 UB 产生。 9 | 10 | 第二种方式,对 Unsafe 代码进行安全抽象,是 Rust 生态的一种约定俗成。 11 | 12 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/safe_abstract/P.UNS.SAS.06.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.SAS.06 不要随便在公开的 API 中暴露裸指针 2 | 3 | **【描述】** 4 | 5 | 在公开的API中暴露裸指针,可能会被用户修改为空指针,从而有段错误风险。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | use cache; 11 | 12 | 13 | /** 14 | 15 | `cache crate` 内部代码: 16 | 17 | ```rust 18 | pub enum Cached<'a, V: 'a> { 19 | /// Value could not be put on the cache, and is returned in a box 20 | /// as to be able to implement `StableDeref` 21 | Spilled(Box), 22 | /// Value resides in cache and is read-locked. 23 | Cached { 24 | /// The readguard from a lock on the heap 25 | guard: RwLockReadGuard<'a, ()>, 26 | /// A pointer to a value on the heap 27 | // 漏洞风险 28 | ptr: *const ManuallyDrop, 29 | }, 30 | /// A value that was borrowed from outside the cache. 31 | Borrowed(&'a V), 32 | } 33 | **/ 34 | fn main() { 35 | let c = cache::Cache::new(8, 4096); 36 | c.insert(1, String::from("test")); 37 | let mut e = c.get::(&1).unwrap(); 38 | 39 | match &mut e { 40 | cache::Cached::Cached { ptr, .. } => { 41 | // 将 ptr 设置为 空指针,导致段错误 42 | *ptr = std::ptr::null(); 43 | }, 44 | _ => panic!(), 45 | } 46 | // 输出:3851,段错误 47 | println!("Entry: {}", *e); 48 | } 49 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/safe_abstract/P.UNS.SAS.07.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.SAS.07 在抽象安全方法的同时,也建议为性能考虑而增加相应的 Unsafe 方法 2 | 3 | **【描述】** 4 | 5 | 在 Rust 标准库中有很多后缀有 `_unchecked` 的方法,都对应一个没有该后缀的同名方法,比如 `get() / get_unchecked()`。 6 | 7 | **【正例】** 8 | 9 | ```rust 10 | /// 假如调用环境可以保证地址是非空,那么可以使用这个 "_unchecked" 的函数 11 | #[inline(always)] 12 | unsafe fn io_read_u32_unchecked(ioaddr: usize) -> u32 { 13 | let val = ptr::read_volatile(ioaddr as *const u32); 14 | trace!("io_read_u32 {:#x}={:#x}", ioaddr, val); 15 | val 16 | } 17 | 18 | /// 安全抽象版本 19 | #[inline(always)] 20 | fn io_read_u32() -> Result { 21 | let ioaddr = ioaddr as *const u32; 22 | if ioaddr.is_null() { 23 | return Err(MyError::Content("io_read_u32 addr is null!")); 24 | } 25 | unsafe { 26 | let val = ptr::read_volatile(ioaddr); 27 | trace!("io_read_u32 {:#x}={:#x}", ioaddr, val); 28 | ok(val) 29 | } 30 | } 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/union.md: -------------------------------------------------------------------------------- 1 | # 联合体(Union) 2 | 3 | Union 是没有 tag 的 Enum,Enum 是有 tag 的Union 。 4 | 5 | 内存布局 Union 和 Enum 相似。 6 | 7 | 正因为没有 tag,Rust 编译器无法检查当前使用的正是哪个变体,所以,访问 Union 的变体是 Unsafe 的。 8 | 9 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/union/P.UNS.UNI.01.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.UNI.01 除了与 C 交互,尽量不要使用 Union 2 | 3 | **【描述】** 4 | 5 | Rust 支持 Union 就是为了调用 C 接口。如果不是 FFi ,就避免使用 Union。 6 | 7 | 一般情况下请使用 枚举 或 结构体代替。 8 | 9 | 使用 `Copy` 类型的值和 `ManuallyDrop` 来初始化 Union 的变体,不需要使用 Unsafe 块。 10 | 11 | **【反例】** 12 | 13 | ```rust 14 | union MyUnion { 15 | f1: u32, 16 | f2: f32, 17 | } 18 | ``` 19 | 20 | **【正例】** 21 | 22 | ```rust 23 | #[repr(C)] 24 | union MyUnion { 25 | f1: u32, 26 | f2: f32, 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/unsafe_rust/union/P.UNS.UNI.02.md: -------------------------------------------------------------------------------- 1 | ## P.UNS.UNI.02 不要把联合体的不同变体用在不同生命周期内 2 | 3 | **【描述】** 4 | 5 | 对联合体的变体进行借用的时候,要注意其他变体也将在同一个生命周期内。抛开内存布局、安全性和所有权之外,联合体的行为和结构体完全一致,你可以将联合体当做结构体来进行判断。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // ERROR: cannot borrow `u` (via `u.f2`) as mutable more than once at a time 11 | fn test() { 12 | let mut u = MyUnion { f1: 1 }; 13 | unsafe { 14 | let b1 = &mut u.f1; 15 | // ---- first mutable borrow occurs here (via `u.f1`) 16 | let b2 = &mut u.f2; 17 | // ^^^^ second mutable borrow occurs here (via `u.f2`) 18 | *b1 = 5; 19 | } 20 | // - first borrow ends here 21 | assert_eq!(unsafe { u.f1 }, 5); 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/variables.md: -------------------------------------------------------------------------------- 1 | # 3.3 本地变量 2 | 3 | 这里所说的变量单指局部变量而不包括全局变量。 默认情况下,Rust 会强制初始化所有变量的值,以防止使用未初始化的内存。 4 | 5 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/variables/G.VAR.01.md: -------------------------------------------------------------------------------- 1 | ## G.VAR.01 以解构元组方式定义超过四个变量时不应使用太多无意义变量名 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | 在以解构元组的方式定义超过四个变量时,变量名可能是无特别语义的,如用单个字符表示的临时变量。但是不宜使用过多无意义变量名。 8 | 9 | **【反例】** 10 | 11 | ```rust 12 | #![warn(clippy::many_single_char_names)] 13 | // 不符合 14 | let (a, b, c, d, e, f, g) = (...); 15 | ``` 16 | 17 | **【正例】** 18 | 19 | 元组元素超过四个的,建议使用包含语义的变量名。 20 | 21 | ```rust 22 | #![warn(clippy::many_single_char_names)] 23 | // 符合 24 | let (width, high, len, shape, color, status) = (...); 25 | ``` 26 | 27 | **【Lint 检测】** 28 | 29 | | lint name | Clippy 可检测 | Rustc 可检测 | Lint Group | level | 30 | | ------------------------------------------------------------------------------------------------ | ------------- | ------------ | ---------- | ----- | 31 | | [many_single_char_names](https://rust-lang.github.io/rust-clippy/master/#many_single_char_names) | yes | no | pedantic | allow | 32 | 33 | 该 lint 对应 `clippy.toml` 配置项: 34 | 35 | ```toml 36 | # 修改可以绑定的单个字符变量名最大数量。默认为 4 37 | single-char-binding-names-threshold=4 38 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/variables/G.VAR.04.md: -------------------------------------------------------------------------------- 1 | ## G.VAR.04 避免因局部变量过大而导致的大量栈分配 2 | 3 | **【级别】** 建议 4 | 5 | **【描述】** 6 | 7 | Rust 局部变量默认分配在栈上。当局部变量占用栈空间过大时,会栈溢出。 8 | 9 | 采用`Box`分配也可能出现栈溢出,参见[issues #53827](https://github.com/rust-lang/rust/issues/53827),因为目前 `Box`的行为是先在栈上分配然后再复制到堆上。 10 | 11 | Rust 默认栈分配空间为: 12 | 13 | 1. 主线程默认 `8MiB` 。 14 | 2. 运行中代码创建的子线程默认是 `2MiB` 。 15 | 16 | 也可以自行配置栈分配内存大小。 17 | 18 | 所以,局部变量占用多少空间才算过大,这个需要开发者根据具体的场景根据栈大小配置情况做出合适的预判,一般以 512 KiB为宜。 19 | 20 | **【反例】** 21 | 22 | ```rust 23 | fn main() { 24 | // 不符合:运行时会栈溢出 25 | let a = [-1; 3000000]; 26 | // or 27 | // 不符合:运行时会栈溢出 28 | let a = Box::new([-1; 3000000]); 29 | } 30 | ``` 31 | 32 | **【正例】** 33 | 34 | ```rust 35 | // 符合:栈大小适中 36 | let _: [i32; 8000] = [1; 8000]; 37 | ``` 38 | -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/variables/P.VAR.01.md: -------------------------------------------------------------------------------- 1 | ## P.VAR.01 一般情况下避免先声明可变变量再赋值 2 | 3 | **【描述】** 4 | 5 | 一般情况下,不要先声明一个可变的变量,然后在后续过程中再去改变它的值。声明一个变量的时候,要对其进行初始化。如果后续可能会改变其值,要考虑优先使用变量遮蔽(继承式可变)功能。如果需要在一个子作用域内改变其值,再使用可变绑定或可变引用。 6 | 7 | **【反例】** 8 | 9 | ```rust 10 | // 不符合 11 | let mut base : u8; 12 | if cfg!(not(USB_PROTOCOL_NEW_ARCH)) { 13 | base = other_instance.base; 14 | } else { 15 | base = 42u8; 16 | } 17 | ``` 18 | 19 | **【正例】** 20 | 21 | ```rust 22 | // 符合 23 | let base : u8 = if cfg!(not(USB_PROTOCOL_NEW_ARCH)) { 24 | other_instance.base 25 | } else { 26 | 42u8 27 | } 28 | ``` -------------------------------------------------------------------------------- /src/safe-guides/coding_practice/variables/P.VAR.02.md: -------------------------------------------------------------------------------- 1 | ## P.VAR.02 利用变量遮蔽功能保证变量安全使用 2 | 3 | **【描述】** 4 | 5 | 在某些场景,可能会临时准备或处理一些数值,但在此之后,数据只用于检查而非修改。 6 | 7 | 那么可以将其通过变量遮蔽功能,重新绑定为不可变变量,来表明这种临时可变,但后面不变的意图。 8 | 9 | 10 | **【反例】** 11 | 12 | ```rust 13 | // 不符合:代码语义上没有表现出来先改变,后不变那种顺序语义 14 | let data = { 15 | let mut data = get_vec(); 16 | data.sort(); 17 | data 18 | }; 19 | 20 | // `data` 在后面不会再被改变 21 | ``` 22 | 23 | **【正例】** 24 | 25 | ```rust 26 | // 符合 27 | let mut data = get_vec(); 28 | data.sort(); //临时需要排序 29 | let data = data; // 符合: 后面就不需要改动了,由编译器可以确保 30 | 31 | // `data` 在后面不会再被改变 32 | ``` 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------