bigywater.tech
Rust · 162

rustfmt 格式化

格式化工具会不会改变程序行为?反复运行结果稳定吗?配置怎么影响输出?想保留手工排版怎么办?这篇用真实的 rustfmt 与汇编哈希逐个验证。

1 / 0
rustfmt --check 在有差异 / 已格式化时的退出码
f43810d33a73
格式化前后相同的汇编哈希(语义不变)
12 对 5 行
max_width = 40 与 100 时同一段代码的行数

--check

实测 · 本机执行输出
  退出码 1;输出行数 52
    Diff in <实验目录>/proj/u162/src/main.rs:1:
    -
    -use std::collections::HashMap;use std::fmt;
    -use std::io::{self,Read};
    -struct Point{x:i32,y:i32}
    -impl fmt::Display for Point{fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{write!(f,"({}, {})",self.x,self.y)}}
    -fn long_function_name_with_many_parameters(first_parameter: i32, second_parameter: String, third_parameter: Vec<u8>, fourth: Option<i32>) -> i32 { fi
    -fn main(){
    -    let mut m=HashMap::new();m.insert("a",1);
    -    let v=vec![1,2,3];   let total:i32=v.iter().map(|x|x*2).sum();
    -    if total>5{println!("big {}",total);}else{println!("small")}
    -    let p=Point{x:1,y:2};println!("{}",p);
    -    let _=io::stdin().read(&mut []);let _=long_function_name_with_many_parameters(1,"x".to_string(),vec![1],None);
    +use std::collections::HashMap;

静态检查工具见Clippy 静态检查;CI 里的完整用法见Rust 项目持续集成。rustfmt 1.9.0。

语义不变

实测 · 本机执行输出
  格式化前 汇编哈希 f43810d33a73(983 行)  格式化后 f43810d33a73(983 行)  相同: True
  格式化后的代码(13 行 → 38 行):
    use std::collections::HashMap;
    use std::fmt;
    use std::io::{self, Read};
    struct Point {
        x: i32,
        y: i32,
    }
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "({}, {})", self.x, self.y)
        }
    }
    fn long_function_name_with_many_parameters(
        first_parameter: i32,
        second_parameter: String,
        third_parameter: Vec<u8>,
        fourth: Option<i32>,
    ) -> i32 {
        first_parameter
            + second_parameter.len() as i32
            + third_parameter.len() as i32
            + fourth.unwrap_or(0)

幂等

实测 · 本机执行输出
  第二次格式化后是否与第一次相同:True
  再 --check:退出码 0

rustfmt.toml

实测 · 本机执行输出
  max_width = 40   → 12 行
      fn main() {
          let values = vec![
              1000, 2000, 3000, 4000, 5000,
              6000, 7000, 8000,
          ];
          let total: i32 = values
              .iter()
              .map(|x| x * 2)
              .filter(|x| *x > 3000)
              .sum();
          println!("total is {}", total);
      }
  max_width = 100  → 5 行
      fn main() {
          let values = vec![1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000];
          let total: i32 = values.iter().map(|x| x * 2).filter(|x| *x > 3000).sum();
          println!("total is {}", total);
      }
  max_width = 140  → 5 行
      fn main() {
          let values = vec![1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000];
          let total: i32 = values.iter().map(|x| x * 2).filter(|x| *x > 3000).sum();
          println!("total is {}", total);
      }

跳过与 nightly 选项

实测 · 本机执行输出
  格式化后:
    fn main() {
        #[rustfmt::skip]
        let m = [
            1,0,0,
            0,1,0,
            0,0,1,
        ];
        let n = [1, 0, 0, 0, 1, 0];
    }
  在 rustfmt.toml 里写 nightly 才有的选项 imports_granularity:
    Warning: can't set `imports_granularity = Crate`, unstable features are only available in nightly channel.

分步演示

rustfmt
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。rustfmt 1.9.0(稳定版)
    • 没有验证:nightly 选项(imports_granularity、group_imports、wrap_comments 等)的实际效果、cargo fmt 对工作区的处理、对宏内部代码的格式化限制、与 rust-analyzer 保存时格式化的集成、格式化失败(语法错误)时的行为
    京ICP备2026067502号-1京公网安备11010502063487号