Rust代码静态分析工具Clippy浅析

简介

近期主要在用Rust,组内的技术栈也逐渐“锈化”,所以Rust的一些基础设施,如CI、编程规范等也逐步开始落实。

编程规范对于每个开发团队来说都很重要,除了能够保证代码的基本质量和可读性之外,还体现了团队的流程规范性。但就我遇到的现实是,在高速的版本迭代过程中,很难能够让所有同学保持对编程规范的熟悉和遵守,常常是精心准备的编程规范无法落地。基于此Rust给了我们现代语言的解决方案——Clippy和Rustfmt。

Clippy,它是一款由社区维护,集成在Rust编译器中的静态代码分析工具,其主要作用包括检测代码潜在问题(虽然编译器已经足够强大,但是还不够)、对代码提供改进建议等。因为集成在编译器中,其能够很好的和现有的CI流水线集成,在每次代码更新之后运行Clippy检测代码。同时社区编写了众多的Lint,可以挑一些规范应用在你的工程中,Clippy会在运行时检测新代码是否遵守你挑选的这些规范,根据配置的不同,可以对违反规则的代码抛出警告或者直接让CI流水线失败。如果官方提供的Lint 集不符合你的编程哲学你也可以写自己的Lint 集。

综上,Clippy是一款能够很好落实编程规范,同时自由度又非常高的工具,所以——学它!

Clippy使用

作为Rust编译器的一部分,我们一般通过Cargo子命令的形式调用Clippy。在你的Rust工程下输入cargo clippy即可运行,Clippy使用默认的Lint 集对你的代码进行检测,当然这是最简单的调用。

Lint 集

Lint 集可以理解为众多Lint 组的集合,每个组中都有若干规范,目前Rust 1.81.0官方有9个Lint 组,600+条规范。

  • clippy::correctness:默认Lint级别Deny(含义后面介绍)。correctness是Lint 集中唯一默认为Deny的组,只要违反其中的规则,Clippy就会直接报error,且理由非常充分。如果违反了该组中的规则,你应该去修复它。
  • clippy::suspicious:默认Lint级别Warn。suspicious中定义了可能存在问题的代码,如果是故意这样写的,那么应该在源码中通过allow让编译器不再抱怨。
  • clippy::style:默认Lint级别Warn。style中主要定义了一些编码风格上的规则,所以该组比较主观。
  • clippy::complexity:默认Lint级别Warn。complexity组主要会提出一些简化代码的建议,它主要关注如何更短、更易懂且保留语义的去编码。
  • clippy::perf:默认Lint级别Warn。perf会提出一些性能上的建议,主要针对编译器无法轻松优化,但是改动一下就能够帮助编译器进行优化的代码。
  • clippy::pedantic:默认Lint级别Allow。pedantic组非常的严格(吹毛求疵),最好不要完整的提升该组的Lint级别,否则编译器会发出大量抱怨。
  • clippy::restriction:默认Lint级别Allow。restriction组会限制你使用Rust语言的某些部分,不建议提升整个组的Lint级别。
  • clippy::nursery:默认Lint级别Allow。
  • clippy::cargo:默认Lint级别Allow。cargo组会给你一些使用Cargo.toml文件的建议。

以上为目前官方定义的Lint 集,可以访问官方Lint 集查看其完整定义和规则示例。
在这里插入图片描述
在这里插入图片描述

网站对不同版本的Rust编译器所支持的Lint 集进行了分类,选一个你的Rust编译器版本进入,对于每一条规则,网站都对其所属的Lint 组、默认Lint 级别、规则描述以及为什么需要这条规则进行了解释,同时还编写代码进行示例(不得不佩服Rust在设计之初对于文档输出的考虑)。

示例

上面介绍了很多,现在以官方Lint 集中的第一条规则absolute_paths为例进行介绍。

在这里插入图片描述

上图是官方Lint 集中的第一条规则absolute_paths的描述,这条规则定义为,在代码中引用第三方的crate时,不要使用绝对路径进行引用,而是通过use 后使用。
右上角框起来的部分,分别表示这个规则所属的Lint 组和默认Lint 级别。这里absolute_paths规则属于 restriction这个Lint 组;默认Lint 级别为allow——即默认情况下,允许这条规则被违反,换句话说就是cargo clippy运行时如果检测到了代码违反了absolute_paths规则,但是clippy不会发出任何“抱怨”,因为默认情况下这是允许的。值得注意的是restriction组下所有规则的Lint 级别都是allow
示例:

use std::time::Duration;

fn main()
{
    std::thread::sleep(Duration::from_secs(1));
}

有如上代码通过绝对路径引用了Rust std库中的std::thread::sleep函数,当我们运行cargo clippy时:

在这里插入图片描述

可以看到,编译器非常平静安详。这代表我们的代码遵守了Lint集中的所有规则吗?当然不是,这是因为虽然违反了某些规则,但是违反这些规则默认情况下是允许的。
如果我们觉得这条规则很符合我们要定制的编程规范,可以在命令行中提升某条规则的级别——cargo clippy -- -Wclippy::absolute_paths

在这里插入图片描述

cargo clippy -- -Wclippy::absolute_paths命令将absolute_paths规则的Lint 级别提升为 Warn,所以这里再次运行时,因为std::thread::sleep使用了绝对路径引用,编译器会显示warning。

Lint 级别有三种,分别是Allow(允许)、Warn(警告)以及Deny(拒绝)。违反了Deny级别的规则会导致Clippy 运行产生error,退出时的返回码不为0,这在CI中会非常有用。

Tips : cargo clippy – -Dwarnings 命令如果发现了任何警告(包括rustc发现的警告,例如dead_code)都会导致构建失败,返回非0

Lint 级别

实际中,对于Lint 集中的规则我们希望自定义其Lint 级别,因为可能有些规则我们比较认同,但是其默认级别为Allow;有些规则我们不太认同,但是其默认级别又是Warn或者Deny,这导致代码检测结果不干净或者干脆让CI失败了,但是这个规则是不认同的,所以定制化Lint 级别就显得尤为重要。

  • 提升整个Lint组的级别:上面介绍了在命令行中提升某个规则的Lint级别,该命令对于整个Lint组也适用。
    cargo clippy – -Wclippy::restriction将整个restriction组下所有规则的Lint 级别提升为Warn,但是最好不要偷懒启用整个组,因为有时候有些组内的多条规则可能是矛盾的,你应该挑选某些规则,然后提升其级别用作你的规范。
  • 源文件通过属性方式设置
    • 限制于单个函数中
      use std::time::Duration;
      
      #[warn(clippy::absolute_paths)]
      fn test_fun () {
          std::thread::sleep(Duration::from_secs(1));
      }
      fn main(){
          test_fun();
          std::thread::sleep(Duration::from_secs(1));
      }
      
      以上运行cargo clippy检查时,针对test_fun 函数,absolute_paths规则的等级将会提升为Warn。和命令行类似,这种方法也可以将规则的名字替换为Lint组的名字,这样做将会提升整个组的级别为Warn,作用于函数上。
    • 作用于整个工程
      #![warn(clippy::absolute_paths)]
      use std::time::Duration;
      
      fn test_fun (){
          std::thread::sleep(Duration::from_secs(1));
      }
      fn main(){
          test_fun();
          std::thread::sleep(Duration::from_secs(1));
      }
      
      当在你工程的main.rs或者lib.rs中设置以上属性,Clippy在检测这个工程的代码时,都会将absolute_paths规则提升为Warn。同样,这里也可以将规则名替换为某个Lint 组名。
  • Cargo.toml 中的 Lint 部分:在Cargo.toml中设置Lint的级别可能更适合编写编程规范的需求。试想,当组内的同学编写好一份统一的Lint级别设置后,统一将其复制到各个工程的Cargo.toml 中,这样不侵入到代码中进行属性设置可能是比较好的一个方案。
    [lints.clippy]
    absolute_paths = "warn"
    
    在你的Cargo.toml中添加lints.clippy块,在该块中可以设置规则的等级。同样的,这里也可以直接设置整个组的Lint级别。
    比较有趣的一点是Cargo.toml 中的 Lint 部分在设置Lint 级别时需要留意优先级的设置。例如,想要将restriction所有的规则降级为allow,但是其中的absolute_paths 规则比较认同,想维持warn。
    [lints.clippy]
    restriction = "allow"
    absolute_paths = "warn"
    
    上面的写法会导致cargo clippy直接报错无法运行,编译器表示将restriction 设置为allow的优先级为0(默认优先级为0),但是将restriction 组内的absolute_paths 规则设置为warn的优先级也为0,所以clippy无法判断absolute_paths 最终的Lint等级是多少,稍加修改。
    [lints.clippy]
    restriction = "allow"
    absolute_paths = { level = "warn", priority = 1 }
    
    上面的写法表示将absolute_paths 规则设置为warn的优先级为1,大于将restriction 组设置为allow的优先级0,所以cargo clippy又可以正产运行了。

总结

clippy原生提供了非常丰富的Lint 集,这些规则满足了我们对编程规范大部分的需求,还有最后一点需求无法满足也可以通过自定义Lint 集(有机会再分享下这个)的方式实现。
同时,基于这套框架,clippy让编程规范不再是空中楼阁,能够很好的在开发环节中落地(我们团队C++编程规范坟头草都有三米了)。


欢迎大家关注
微信公众号:zl.rs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zl.rs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值