Rustアトリビュート活用法!コンパイラへの指示からテストまで幅広く説明
Rustのアトリビュートは、関数や構造体などの項目に追加情報を注釈することができる強力な機能です。 アトリビュートは、コンパイラへの指示やテストの効率化など、幅広い分野で活用できます。この記事では、アトリビュートの使い方や活用事例を紹介します。
Rustのアトリビュートとは
Rustのアトリビュートは、コードの構造や振る舞いに関するメタデータを追加するためのアノテーションです。アトリビュートを使うことで、コンパイラやLinterに追加情報を提供し、コンパイル時の挙動を制御することができます。
次のように、アトリビュートを使うことで、コンパイラやLIntを制御することができます。
// testを使ってtest_foo()関数をテスト関数としてマークしている
// cargo testコマンドの実行時にテスト関数を検出してテストの実行がされる
#[test]
fn test_foo() {
/* ... */
}
// cfgを使って条件付きコンパイルを実施
// ターゲットOSがLinuxの場合のみbarというモジュールがコンパイルされる
#[cfg(target_os = "linux")]
mod bar {
/* ... */
}
// allowを使ってunused_variablesのLintを許可している
// unused_variables は未使用の変数の警告が無視される
fn some_unused_variables() {
#![allow(unused_variables)]
let x = ();
let y = ();
let z = ();
}
Rustのアトリビュートの主な使用目的
Rustのアトリビュートはさまざまな目的で使用されますが、主な使用目的は以下のとおりです。
- 条件付きコンパイル:
cfg
- テスト関数のマーク:
test
- 自動的にtraitを実装する:
derive
- Lintの制御:
allow
,warn
,deny
- パフォーマンス最適化:
inline
条件付きコンパイル: cfg
, cfg!
cfg
アトリビュートやcfg!
マクロを使うことで条件付きコンパイルを行うことができます。
cfg
アトリビュートはコンパイラに注釈したコードをコンパイルするかどうかを指示できます。また、cfg!
マクロはbool値を返すのでコード上で条件分岐をすることができます。
// ターゲットOSがLinuxの場合にコンパイルされる
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
println!("You are running linux!");
}
// ターゲットOSがLinux以外の場合にコンパイルされる
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
println!("You are *not* running linux!");
}
fn main() {
are_you_on_linux();
println!("Are you sure?");
// cfg!マクロはbool値を返す
if cfg!(target_os = "linux") {
println!("Yes. It's definitely linux!");
} else {
println!("Yes. It's definitely *not* linux!");
}
}
// 出力(※Macで実行)
// You are *not* running linux!
// Are you sure?
// Yes. It's definitely *not* linux!
テスト関数のマーク: test
test
アトリビュートを使うことで、テスト関数をマークすることができます。テスト関数はテストモードのとき(rustc --test
やcargo test
など)のみコンパイルされ、テスト時に自動的にテスト関数が実行されます。
test
アトリビュート一緒に使われるアトリビュートとして、以下のアトリビュートもあります。
ignore
:テストを実行しないようにする。should_panic
:テストでパニックが発生した場合のみパスさせる
// src/lib.rs
pub fn add_two(x: i32) -> i32 {
x + 2
}
// 条件付きコンパイラでテスト時のみtestsモジュールがコンパイルされる
#[cfg(test)]
mod tests {
use super::*;
// testアトリビュートにより、it_add_two()をテスト関数としてマークしている
#[test]
fn it_add_two() {
let result = add_two(1);
assert_eq!(result, 3);
}
// ignoreアトリビュートにより、it_ignored()を実行しないようにしている
#[test]
#[ignore = "not implemented yet"]
fn it_ignored() {
assert_eq!(5, 5);
}
// should_panicアトリビュートにより、it_should_panic()がpanicすることを期待している
#[test]
#[should_panic(expected = "something wrong happened!!")]
fn it_should_panic() {
panic!("something wrong happened!!");
}
}
自動的にtraitを実装する: derive
derive
アトリビュートは、Rustの標準ライブラリに含まれる一部のトレイトの実装を自動的に生成することができます。 derive
アトリビュートは、Debug
、PartialEq
、Clone
トレイトとよく使われます。
Debug
トレイトの実装
Debug
トレイトは、デバッグ情報を表示するためのトレイトです。次のようにderive
アトリビュートを使って、Debug
トレイトを自動的に実装することができます。
// deriveを使用して、Point構造体にDebugトレイトを自動的に実装
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
// {:?}でデバッグ表示
println!("{:?}", p);
// Point { x: 3.0, y: 4.0 }
}
PartialEq
トレイトの実装
PartialEq
トレイトは、値の比較を行うためのトレイトで、==
と!=
演算子をサポートできます。
// deriveを使用し、Point構造体にPartialEqトレイトを自動的に実装
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 3, y: 4 };
let p2 = Point { x: 3, y: 4 };
let p3 = Point { x: 5, y: 6 };
// == と != 演算子を利用できるようになる
assert!(p1 == p2);
assert!(p1 != p3);
}
Clone
トレイトの実装
Clone
トレイトは、オブジェクトの複製を作成するためのトレイトです。
// deriveを使用し、Point構造体にCloneトレイトとDebugトレイトを自動的に実装
#[derive(Clone, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 3, y: 4 };
let p2 = p1.clone(); // clone()が利用できるになる
println!("p1: {:?}", p1); // p1: Point { x: 3, y: 4 }
println!("p2: {:?}", p2); // p2: Point { x: 3, y: 4 }
}
Lintの制御: allow
, warn
, deny
Lintを使うことで、デットコードや推奨されないコーディングスタイルやコーディングパターンをチェックすることができます。Rustのallow
、warn
、deny
アトリビュートを使うことでLintチェックを制御できます。
allow
はLintのwarningをださないようにすることができます。コーディングをしていて、どうしてもLintに違反してしまう書き方をするケースで使います。ないにこしたことはないですが。。
#[allow(unused_variables)]
fn main() {
let x = 42; // この変数は使われていないが、warningは出ない
}
warn
はLintのwarningをだすようにします。warningレベルなのでコンパイルは通ります。
#[warn(unused_variables)]
fn main() {
let x = 42; // この変数は使われていないため、warningが出る
}
deny
はLintに違反する場合はコンパイルを失敗させます。
#[deny(unused_variables)]
fn main() {
let x = 42; // この変数は使われていないため、コンパイルエラーが発生する
}
パフォーマンス最適化: inline
Rustのinline
アトリビュートは、コンパイラに関数のインライン展開をするように提案するために使われます。
インライン展開とは、関数の呼び出しを、その関数の本体に置き換える最適化手法です。これにより、関数呼び出しに伴うオーバーヘッドを減らすことができますが、コードサイズが大きくなる可能性があります。
// inlineによりインライン展開を提案する
// あくまで提案なので、インライン展開をするかどうかはコンパイラが決める
#[inline]
fn fast_function(x: i32, y: i32) -> i32 {
x + y
}
fn main() {
let result = fast_function(1, 2);
println!("Result: {}", result);
}