Rustコンパイラ組込みのlintについて調べる
はじめに
Rustコンパイラであるrustc
には組込みのlintがあります。
Rustのlintツールでは、clippyが有名ですが、rustc組込みの方もソースコードの改善に役立ちます。
7月の技術書同人誌博覧会のネタ探しの一環だったのですが、思ったより世の中の役に立ちそうだったので、ドラフトを公開します。 原稿落とさなければ、「組込み / ベアメタルRustテクニック集」的な薄い本を出す予定です。
ちなみにここに書いている内容は、全てThe rustc bookに書いてあります。
lint
lint
はソースコードをコンパイラより厳密なルールに則り、検査するためのツールです。
Rustコンパイラには、様々なlintルールが組み込まれています。
ソースコードをコンパイルする時、自動的にlintによる検査が行われます。
プロジェクトの運用ルールに合わせて、適切なlintルールを設定することで、ソースコードの品質をより向上できるでしょう。
lintレベル
rustcのlintレベルは、4つに分類されます。
- allow (許可)
- warn (警告)
- deny (拒絶)
- forbid (禁止)
各lintルールには、デフォルトのlintレベルがあり、コンパイルオプションかアトリビュートで上書きできるようになっています。 まず、lintレベルについて説明します。
allow (許可)
lintルールを適用しません。 例えば、次のコードをコンパイルしても、警告は発生しません。
pub fn foo() {}
$ rustc lib.rs --crate-type=lib
しかし、このコードはmissing_docs
ルールを違反しています。
lintレベルを上書きしてコンパイルすると、コンパイルエラーになったり、警告が出力されるようになります。
warn (警告)
lintルール違反があった場合、警告を表示します。
fn main() { let x = 5; }
このコードは次の警告が報告されます。
warning: unused variable: `x` --> src/main.rs:2:9 | 2 | let x = 5; | ^ help: consider prefixing with an underscore: `_x` | = note: #[warn(unused_variables)] on by default
deny (拒絶)
lintルール違反があった場合、コンパイルエラーになります。
fn main() { 100u8 << 10; }
このコードは、exceeding_bitshifts
ルールに違反しており、コンパイルエラーになります。
error: attempt to shift left with overflow --> src/main.rs:2:5 | 2 | 100u8 << 10; | ^^^^^^^^^^^ | = note: #[deny(exceeding_bitshifts)] on by default
forbid (禁止)
lintルール違反があった場合、コンパイルエラーになります。 forbidは、denyより強いレベルで、上書きができません。
下のコードは、アトリビュートでmissing_docs
ルールをallowに上書きしています。
#![allow(missing_docs)] pub fn foo() {}
missing_dogs
ルールを、denyレベルに設定してコンパイルすると、このコードはコンパイルできます。
$ rustc lib.rs --crate-type=lib -D missing-docs
一方、forbidレベルに設定してコンパイルすると、コンパイルエラーになります。
$ rustc lib.rs --crate-type=lib -F missing-docs error[E0453]: allow(missing_docs) overruled by outer forbid(missing_docs) --> lib.rs:1:10 | 1 | #![allow(missing_docs)] | ^^^^^^^^^^^^ overruled by previous forbid | = note: `forbid` lint level was set on command line
lintレベルの設定方法
コンパイラフラグで設定
コンパイルオプションで、-A
, -W
, -D
, -F
のいずれかを指定して、lintレベルを設定できます。
$ rustc lib.rs --crate-type=lib -W missing-docs
もちろん、複数のフラグを同時に設定することも可能です。
$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables
Cargoの設定ファイル内で、lintレベルを設定することも可能です。
$ cat .cargo/config
[build] rustflags = ["-D", "unsafe-code"]
アトリビュートで設定
ソースコード内のアトリビュートで、allow
, warn
, deny
, forbid
のいずれかを指定して、lintレベルを設定できます。
$ cat lib.rs #![warn(missing_docs)] pub fn foo() {}
1つのアトリビュートに、複数のlintルールを指定できます。
#![warn(missing_docs, unused_variables)] fn main() { pub fn foo() {} }
複数のアトリビュートを組み合わせて使うこともできます。
#![warn(missing_docs)] #![deny(unused_variables)] pub fn foo() {}
lintルール
次のコマンドでlintルールと、デフォルトレベルの一覧が取得できます。
$ rustc -W help
デフォルトレベルごとに、サンプルコード付きでlintルールが説明されています。
知られざるlintルールたち
ちらっと目について、へー、と思ったルールを独断と偏見で紹介します。
ちょっとマニアック
- unsafe-code:
unsafe
ブロックがある。デフォルトallow。unsafe
ブロックを使わせたくないアイツの.cargo/config
にこっそりどうぞ。 - illegal-floating-point-literal-pattern: パターン内で浮動小数点が使われている。デフォルトwarn。
- no-mangle-generic-items: ジェネリック関数にno_mangleアトリビュートをつける。デフォルトwarn。
warn
なんだ?強制的にマングルされるってことかしら?
自分が知らんかった枠
- variant-size-differences: enumのヴァリアントのサイズが違いすぎる。デフォルトallow。
- improper-ctypes:
extern "C"
の中などFFIでRustの型を使う。デフォルトwarn。 - no-mangle-const-items: シンボルを持たない
const
変数にno_mangleアトリビュートをつける。デフォルトdeny。言われると納得。やったことないけど。