Rust 小ネタ: Debug トレイトを利用した等価比較
通常、値を (非)等価比較 したいときは PartialEq
トレイトを付ける必要がありますが、本記事では Debug
トレイトを用いた等価比較の方法と、その注意点について紹介します。
#[derive(Debug)]
struct S;
let foo = S;
let bar = S;
assert!(format!("{foo:?}") == format!("{bar:?}"));
ユーザー定義型の等価比較
ユーザー定義型とは、 i32
や char
などの言語組み込みのプリミティブ型とは異なり、 struct
や enum
といった ユーザーが定義する型 のことです。
これらの型は、比較処理や文字列表現の方法がデフォルトで定まっていないため、 PartialEq
トレイトや Debug
トレイトを自身で実装する必要があります。
比較処理が出てくる場面は主に2つです。
- if式などの条件
-
assert_eq!
マクロなどによる等価性の検証
後者は比較によるアサーションが失敗した時に、値をデバッグ形式で出力するので、Debug
トレイトも実装しなければいけません。
Debug
トレイトに関しては、デバッグでとても有用であるため、構造体を定義するのと同時に実装する方も多いのではないでしょうか。
一方、 PartialEq
トレイトは比較処理が必要になるまで実装しません(自分は)。
実装していない状態で比較を試みると、コンパイルエラーになります。
#[derive(Debug)]
struct Foo {
bar: i32,
}
let foo = Foo { bar: 1 };
assert!(foo == Foo { bar: 1 });
error[E0369]: binary operation `==` cannot be applied to type `Foo`
|
| assert!(foo == Foo { bar: 1 });
| --- ^^ -------------- Foo
| |
| Foo
|
note: an implementation of `PartialEq` might be missing for `Foo`
|
| struct Foo {
| ^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `Foo` with `#[derive(PartialEq)]`
|
+ #[derive(PartialEq)]
| struct Foo {
|
Debug
のみでの等価比較: format!
マクロを使う
この時、 PartialEq
を実装するのではなく、PartialEq
を実装した別の型へ変換することで、コンパイルエラーを回避できます。
具体的には、format!
マクロを使って値を String
型へ変換し、比較を行います。
#[derive(Debug)]
struct Foo {
bar: i32,
}
let foo = Foo { bar: 1 };
- assert!(foo == Foo { bar: 1 });
+ assert!(format!("{foo:?}") == format!("{:?}", Foo { bar: 1 }));
PartialEq
を実装する: cfg_attr
属性の利用
必要時のみ 「プロダクションコードでは比較処理は不要だが、テストコードでは assert_eq!
で検証したい」という場合、 cfg_attr
属性が役立ちます。
これにより、テスト時のみ PartialEq
トレイトを実装することができ、リリースバイナリのサイズを抑えられます。
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
struct Foo {
bar: i32,
}
#[test]
fn test() {
let foo = Foo { bar: 1 };
assert!(foo == Foo { bar: 1 });
}
まとめ
Debug
トレイトを利用した等価比較のテクニックを紹介しました。
ただし、以下の点に注意する必要があります。
- 比較に必要な情報が欠落している可能性がある
- 別の型への変換コストが発生する
したがって、比較処理が必要な場合は、PartialEq
トレイトを適切に実装するべきです。
cfg_attr
や cfg
といった 条件付きコンパイル は、他にも様々な条件を指定できます。それらを活用してよりスマートなコードを書きましょう!
……とはいえ、デバッグで手軽に検証したい時なんかは Debug
トレイトを利用した方法が楽で良いんじゃないでしょうか。
ここまで書いてなんですが、リリースビルドでは未使用の実装が最適化によって削除される可能性があるため、何も考えずに PartialEq
くらいは実装しても良いとも思いました。
Discussion