我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

选题:“Cargo 工具链:cargo fmt 代码格式化”
目标:系统讲清 cargo fmt / rustfmt 的工作方式、稳定与不稳定配置项、Workspace 跨包管理、宏/注释/导入整理等复杂场景处理;并给出工程级落地方案:统一团队风格Git Hook 本地拦截CI 严格检查编辑器集成增量引入旧仓库对第三方/生成文件的排除
读完你将能把“代码风格问题”完全交给工具,只为语义争论,不为空格换行争论

1. 为什么一定要上 cargo fmt

  • 一致性:统一排版会显著降低 Code Review 的“噪音”,把注意力集中在逻辑与 API 设计上。
  • 可维护性:自动格式化天然适合“随手保存自动修复”,减少提交前“手动对齐”的时间。
  • 可扩展性:Workspace/Monorepo 模式下,跨 crate 风格统一只需一份 rustfmt.toml
  • 工具生态:Rust Analyzer、Clippy、CI、pre-commit 等都能无缝配合 rustfmt

2. 基础命令与安装

Rust 稳定版默认附带 rustfmt 组件;如果没有,执行:

rustup component add rustfmt

最常用命令:

# 格式化当前 crate
cargo fmt

# 仅检查是否已格式化(CI 常用,非 0 退出会使流水线失败)
cargo fmt -- --check

# 指定格式化某个文件(路径相对项目根)
cargo fmt -- src/lib.rs

备注:cargo fmt 背后调用的就是 rustfmt-- 之后的参数会原封不动传给 rustfmt 可执行文件。

3. 在哪里放配置?rustfmt.toml 的作用域

  • 仓库根目录rustfmt.toml(或 rustfmt.toml + .rustfmt.toml,二者取其一)对该目录及子目录生效。
  • Workspace 根:推荐把配置放在 Workspace 顶层,使所有成员 crate 共享同一风格。
  • 子目录覆盖:子 crate 下若存在另一份 rustfmt.toml,会覆盖上层设置(慎用,除非有明确需求)。

4. 稳定配置项精选(生产可放心使用)

创建 rustfmt.toml,示例:

# === 基础风格 ===
edition = "2021"                 # 与 Cargo.toml 中 [package] edition 保持一致
max_width = 100                  # 最大行宽
hard_tabs = false                # 使用空格而非制表符
newline_style = "Unix"           # 统一换行风格: "Unix" | "Windows" | "Native"
use_small_heuristics = "Max"     # 更积极地单行布局,减少“换行噪音”
fn_params_layout = "Compressed"  # 函数参数更紧凑布局

# === 注释与文档 ===
wrap_comments = true                         # 长行注释自动换行
format_code_in_doc_comments = true          # 格式化文档注释中的代码块
normalize_doc_attributes = true             # 统一 doc 属性风格

# === 导入整理 ===
reorder_imports = true
imports_granularity = "Crate"               # "Preserve" | "Item" | "Module" | "Crate"
group_imports = "StdExternalCrate"          # 分组: 标准库/第三方/本地
# 注:上面两项在较新 rustfmt 已稳定;若你的 rustfmt 版本太老,需要升级。

# === 逗号与换行 ===
trailing_comma = "Vertical"                 # 多行尾随逗号,利于 diff
match_block_trailing_comma = true

# === 细节一致性 ===
merge_derives = true                         # 合并派生宏: #[derive(A,B)]
use_field_init_shorthand = true              # 结构体字面量用简写
hex_literal_case = "Lower"                   # 十六进制统一小写
enum_discrim_align_threshold = 20            # 枚举判别值对齐阈值(视觉整洁)

这些选项能覆盖 80% 的团队诉求:行宽导入分组与合并注释换行文档代码块格式化逗号风格等。

5. 不稳定(nightly)功能与启用方式(可选)

如果你需要更多“进阶”选项,可以启用 nightly:

rustup toolchain install nightly
rustup component add rustfmt --toolchain nightly

然后在 rustfmt.toml 增加:

unstable_features = true

再使用:

cargo +nightly fmt

谨慎建议:生产仓库尽量用稳定选项;只有当你明确知道团队都装有 nightly,且需要某个 nightly 开关时再启用。
常用的 nightly 额外项示例(随版本演进会调整,需以 rustfmt --version 对照官方支持表为准):

# 仅示例,可能随版本变化
imports_layout = "Vertical" # 导入布局更激进

6. 复杂场景一:导入整理(use)的真实效果

原始代码(可读性差,易产生合并冲突):

use regex::Regex; use std::io::{self, Read}; use crate::util::error::AppError; use std::collections::HashMap; use anyhow::Result;

格式化后(分组 + 合并 + 排序):

use std::collections::HashMap;
use std::io::{self, Read};

use anyhow::Result;
use regex::Regex;

use crate::util::error::AppError;
  • 标准库(std::)在前,第三方居中,本地 crate:: 在后;
  • 花括号内自动排序;
  • 采用垂直多行 + 尾随逗号策略,后续增删导入,diff 更干净

7. 复杂场景二:宏、属性、长表达式与注释

rustfmt 对宏调用、属性、链式调用、闭包/迭代器会遵循行宽启发式换行策略。例如:

let data = items
    .iter()
    .filter(|x| x.size > LIMIT && x.name.starts_with("rs_"))
    .map(|x| transform(x, config))
    .collect::<Vec<_>>();

max_width 较小时会分行,注释会随相邻语句移动并正确对齐。
极少数地方你不希望 rustfmt 改动,可用最小粒度的跳过

#[rustfmt::skip]
fn tricky_generated_code() { /* 生成器产物,保持原样 */ }

旧注释式 // rustfmt::skip 已不推荐;优先属性式
文件级别忽略可在 rustfmt.toml 中配置 ignore = ["path/to/generated.rs"]

8. 复杂场景三:文档注释中的代码块

启用 format_code_in_doc_comments = true 后,rustfmt 会尝试格式化形如:

/// Example:
/// ```rust
/// fn add(a:i32,b:i32)->i32{a+b}
/// ```
/// Now call it.

格式化后:

/// Example:
/// ```rust
/// fn add(a: i32, b: i32) -> i32 {
///     a + b
/// }
/// ```
/// Now call it.

这能保持教程/README 里的代码风格与工程一致,也减少 doctest 失败概率

9. Workspace/Monorepo 实战:一次配置,处处生效

仓库结构示例:

my-org/
├─ rustfmt.toml
├─ Cargo.toml            # [workspace]
├─ crates/
│  ├─ service-a/
│  │  └─ Cargo.toml
│  └─ service-b/
│     └─ Cargo.toml
└─ tools/
   └─ codegen/
      └─ Cargo.toml

在 Workspace 根放置 唯一rustfmt.toml,开发者在任意子 crate 下运行 cargo fmt 均会拾取该配置。
生成代码(例如 tools/codegen 产物或第三方同步文件),在根配置中加入:

ignore = [
  "crates/service-a/src/bindings/**",
  "crates/service-b/src/generated/**",
]

ignore 支持相对路径与通配,路径从仓库根解析。


10. 将 cargo fmt 引入旧仓库的“渐进式”方法

一次性格式化全仓库容易造成巨量 diff,影响分支合并。建议:

  1. 冻结主干:在一个窗口内完成全量 cargo fmt,或
  2. 分模块推进:逐个 crate 加入 rustfmt.toml 并提交;
  3. CI 仅检查改动:在 PR 上执行 cargo fmt -- --check,只要开发者改动的文件未格式化就失败;
  4. 一次性“扫尾”:最后找一个空闲窗口,统一跑一遍全仓库 cargo fmt 并合入主干。

11. 本地预提交 Hook:把风格问题拦在门外

Git 原生 Hook(跨平台简洁版):

# .git/hooks/pre-commit  (记得 chmod +x)
#!/usr/bin/env bash
set -euo pipefail

# 仅格式化被暂存的 Rust 文件
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.rs$' || true)
if [ -n "$FILES" ]; then
  echo "[pre-commit] Running cargo fmt on staged files..."
  cargo fmt -- $FILES
  # 重新把格式化后的文件加入暂存区
  echo "$FILES" | xargs git add
fi

echo "[pre-commit] OK"

如果你更喜欢 pre-commit 框架(Python 社区常用),可在 .pre-commit-config.yaml 中配置一个 local 钩子调用 cargo fmt -- --check 或对 staged 文件逐个执行 rustfmt


12. CI 严格检查:GitHub Actions 示例

创建 .github/workflows/ci.yml

name: CI

on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  fmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - name: Check formatting
        run: cargo fmt -- --check

  clippy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - name: Clippy (deny warnings)
        run: cargo clippy --all-targets -- -D warnings

将“风格不合规范”和“警告即错误”一并卡住,提交上云前就干净

13. 编辑器集成:VS Code / Rust Analyzer 设置

VS Code settings.json 建议:

{
  "editor.formatOnSave": true,
  "rust-analyzer.checkOnSave.command": "clippy",
  "rust-analyzer.rustfmt.extraArgs": [],
  "rust-analyzer.cargo.extraEnv": {
    "RUSTFLAGS": ""
  }
}
  • 启用 formatOnSave 即保存即格式化;
  • rust-analyzer.rustfmt.extraArgs 在少数需要传参的场景(如 --edition 2021)可用,一般不必;
  • 若使用 nightly 格式化,可以在 Workspace 的 VS Code 设置里把 rustfmt 工具链切到 nightly(或在命令面板直接运行 cargo +nightly fmt )。

14. 与 Clippy、Lint、生成代码协同

  • 顺序:推荐先 cargo fmt,再 cargo clippy,最后 cargo test
  • 生成代码:对 build.rs 或代码生成器产物,若排版很难看但又不可控,使用 ignore 路径排除即可。
  • Doc 测试:开启 format_code_in_doc_comments 后,文档内代码块风格与工程一致,doc test 通过率更高。

15. 实战代码:通过配置项“可证明”地减少 diff

先写一个“凌乱”的示例文件 src/lib.rs

use regex::Regex; use std::io::{self,Read};
use std::{fmt,fs};
use anyhow::Result; use crate::util::error::AppError;

pub fn greet(name:String)->String{
   format!("Hello, {name}!")
}

pub fn parse(input:&str)->Result<Vec<String>>{
   let re=Regex::new(r#"[A-Za-z_]\w*"#).unwrap();
   let mut out=Vec::new();
   for cap in re.captures_iter(input){
       out.push(cap.get(0).unwrap().as_str().to_string());
   }
   Ok(out)
}

配置 rustfmt.toml(摘自第 4 节)后,运行:

cargo fmt

你会得到更稳定、可读的输出(节选):

use std::io::{self, Read};
use std::{fmt, fs};

use anyhow::Result;
use regex::Regex;

use crate::util::error::AppError;

pub fn greet(name: String) -> String {
    format!("Hello, {name}!")
}

pub fn parse(input: &str) -> Result<Vec<String>> {
    let re = Regex::new(r#"[A-Za-z_]\w*"#).unwrap();
    let mut out = Vec::new();
    for cap in re.captures_iter(input) {
        out.push(cap.get(0).unwrap().as_str().to_string());
    }
    Ok(out)
}

变动点清晰:导入分组与排序、空格/缩进统一、参数/返回值空格对齐、块内换行规则稳定,后续 PR 的 diff 将只围绕逻辑变化。

16. 与团队约定的“可调旋钮”

根据项目类型,你可以在以下“旋钮”上微调:

  • max_width:100/120 常见;CLI 项目或链式调用较多的服务可适当放宽。

  • imports_granularity

    • Crate:以 crate 级合并导入(常用,简洁);
    • Module / Item:更细粒度;
    • Preserve:保持作者原状(不推荐)。
  • use_small_heuristicsMax 倾向一行;Default 稍保守。

  • wrap_comments:长文档多的库建议开启,产品型仓库则按需。

  • newline_style:跨平台协作固定为 Unix,避免 CRLF 带来的无谓 diff。

17. 性能与安全性

  • rustfmt纯格式化,不改动语义,可重复、可确定
  • 格式化速度足以覆盖中大型仓库,通常在 CI 耗时可忽略;
  • 稳定配置项不会“偷偷改变”行为;nightly 开关需团队共识并固定版本。

18. 常见陷阱与排错

  • “CI 过不了:未格式化”:本地先跑 cargo fmt 再提交;若你在 Windows,确保 newline_style = "Unix".gitattributes 保持一致:

    *.rs text eol=lf
    
  • “为何导入没有被合并/分组?”:检查 rustfmt 版本是否过旧;cargo fmt --version 与 CI 环境保持一致。

  • “某段代码不想被改”:用 #[rustfmt::skip] 标注到函数/模块;或在 rustfmt.tomlignore 排除文件。

  • “文档代码块格式怪异”:确认 format_code_in_doc_comments = true,并保证代码块标注了语言(如 ```rust)。

19. 渐进落地路线图(可直接照抄执行)

  1. 在仓库根新增 rustfmt.toml(先用本文推荐模板)。
  2. 本地执行 cargo fmt,提交一次“纯风格修复”的 PR(描述里注明无语义变化)。
  3. 加入 Git Hook(第 11 节脚本),要求每次提交自动格式化。
  4. 增设 CI 检查(第 12 节 --check),防漏网之鱼。
  5. VS Code 启用 formatOnSave,开发流畅无感知。
  6. 对第三方/生成文件目录加入 ignore 清单。
  7. 每季度复查一次配置项与工具版本,尽量停留在稳定通道

20. 结语

cargo fmt 的价值,不在“会不会敲空格”,而在于:让团队从“排版讨论”中解放出来,把注意力投注在 API 的演化与性能/安全的边界上
配合 Workspace 顶层配置、Hook 与 CI,你能把整个工程的风格一次固定,在未来的每一次提交里持续受益。现在就给你的仓库加上一份 rustfmt.toml,让每一行 Rust 既优雅又一致吧!🚀

(未完待续)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐