🎉

Rustのdbg!マクロについて

2021/02/05に公開

dbg! マクロとは

Rust1.32.0 より stable に追加されたマクロです。

https://doc.rust-lang.org/std/macro.dbg.html

その名の通り、デバッグに便利なマクロです。
式と結果が標準エラーに出力されます。
println!などのマクロと違ってそのまま式が出力されます。出力後に値を返してくれるのが大きな違いです。

利用方法

dbg!マクロにて出力する値はDebugトレイトを実装している必要があります。

main.rs
#[allow(dead_code)]
struct S(usize);

fn main() {
    dbg!("rust"); // &strはDebugトレイトを実装している。
    // dbg!(S(0)); // SはDebugトレイトを実装していないためエラー
}
[src/main.rs:5] "rust" = "rust"

Rust Playground

式の結果がDebugトレイトを実装していればよく、式をそのまま渡すこともできます。

main.rs
fn main() {
    dbg!(1 + 2 + 3 + 4);
}
[src/main.rs:2] 1 + 2 + 3 + 4 = 10

Rust Playground

式の結果がCopyトレイトを実装していない場合、値渡しをした場合、元の所有権は失われます。
Copyトレイトを実装してなくても参照渡しをすれば所有権は保持されます。

main.rs
#![allow(unused_variables)]

#[derive(Debug)]
struct S(usize);

fn main() {
    let a = dbg!(S(1 + 2));
    let b = dbg!(a.0 + 1);
    // let c = dbg!(a).0 + 2; // Sはコピートレイトを実装していないためコンパイルエラー
    let d = dbg!(&a.0 + 1); // 参照渡しはOK
}
[src/main.rs:7] S(1 + 2) = S(
    3,
)
[src/main.rs:8] a.0 + 1 = 4
[src/main.rs:10] &a.0 + 1 = 4

Rust Playground

Copyトレイトを実装していれば、元の所有権は残ったままとなります。

main.rs
#![allow(unused_variables)]

#[derive(Copy, Clone, Debug)]
struct S(usize);

fn main() {
    let a = dbg!(S(1 + 2));
    let b = dbg!(a.0 + 1);
    let c = dbg!(a).0 + 2; //SがCopyトレイトを実装しているためOK
    let d = dbg!(&a.0 + 1); // 参照渡しもOK
}
[src/main.rs:7] S(1 + 2) = S(
    3,
)
[src/main.rs:8] a.0 + 1 = 4
[src/main.rs:9] a = S(
    3,
)
[src/main.rs:10] &a.0 + 1 = 4

Rust Playground

実用的な例としては、イテレータの途中経過をデバッグするとき等に利用できます。

main.rs
#![allow(unused_variables)]

fn main() {
    let x = (0..5)
        .map(|x| dbg!(x + 9))
        .map(|x| dbg!(x * 7))
        .fold(0, |acc, x| dbg!(acc + x));
}
[src/main.rs:5] x + 9 = 9
[src/main.rs:6] x * 7 = 63
[src/main.rs:7] acc + x = 63
[src/main.rs:5] x + 9 = 10
[src/main.rs:6] x * 7 = 70
[src/main.rs:7] acc + x = 133
[src/main.rs:5] x + 9 = 11
[src/main.rs:6] x * 7 = 77
[src/main.rs:7] acc + x = 210
[src/main.rs:5] x + 9 = 12
[src/main.rs:6] x * 7 = 84
[src/main.rs:7] acc + x = 294
[src/main.rs:5] x + 9 = 13
[src/main.rs:6] x * 7 = 91
[src/main.rs:7] acc + x = 385

Rust Playground

引数に何もわたさないとdbg!マクロ記述のファイル名と行が出力されます。

main.rs
fn main() {
    dbg!()
}
[src/main.rs:2]

Rust Playground

複数の式を渡すこともできます。戻り値はタプルで帰ってきます。

main.rs
#![feature(type_name_of_val)]
fn main() {
    let a = dbg!(1 + 2, 3 + 4, 5 + 6, 7 + 8);
    dbg!(std::any::type_name_of_val(&a));
}
[src/main.rs:3] 1 + 2 = 3
[src/main.rs:3] 3 + 4 = 7
[src/main.rs:3] 5 + 6 = 11
[src/main.rs:3] 7 + 8 = 15
[src/main.rs:4] std::any::type_name_of_val(&a) = "(i32, i32, i32, i32)"

Rust Playground

dbg!マクロ利用で警告/エラーを表示 - Clippy との連携

dbg!マクロは release ビルド時でも出力されます。
また、バージョン管理システムでの dbg!マクロ利用をエラーにしたいなどがあります。

これらは Rust の lint ツールである Clippy を利用することで対策できます。

https://github.com/rust-lang/rust-clippy

clippy をインストール後、cargo check コマンドのように cargo clippy コマンドを利用できます。
dbg!マクロで警告/エラーを表示する場合、clippy::dbg_macroのレベルを変更することで表示できます。

https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro

設定方法には大きく分けて 2 通りあり、Rust の属性で設定する方法と、cargo clippy のコマンドラインオプションで指定する方法があります。

  1. 属性での指定

    warn属性で警告が発生、deny属性ではエラーが発生します。

    main.rs
    #![warn(clippy::dbg_macro)] // 警告
    // #![deny(clippy::dbg_macro)] // エラー
    fn main() {
      dbg!();
    }
    

    cargo clippyの結果(一部抜粋)

    • 警告時

      warning: `dbg!` macro is intended as a debugging tool
      --> src/main.rs:4:5
      |
      4 |     dbg!();
      |     ^^^^^^
      |
      
    • エラー時

      error: `dbg!` macro is intended as a debugging tool
      --> src/main.rs:4:5
      |
      4 |     dbg!();
      |     ^^^^^^
      |
      

    Rust Playground

  2. コマンドラインオプションでの指定

    Git の pre-commit や CI 等を使いチェックをするならコマンドラインで指定するのが便利です。

    • 警告表示

      cargo clippy -- -W clippy::dbg_macro
      
    • エラー表示

      cargo clippy -- -D clippy::dbg_macro
      

    Git の pre-commit の一例としては以下のような内容になります。

    #!/bin/bash -eu
    
    cargo clean && cargo clippy -- -D clippy::dbg_macro
    

rust-analyzer との連携

dbg!マクロは LSP である rust-analyzer を利用することで便利な機能があります。
rust-analyzer はさまざまなエディタで利用できますが、本記事では VSCode を利用します。

dbg! マクロの挿入

Magic Completions と呼ばれる機能を使うことでお手軽にdbg!マクロを挿入できます。
https://rust-analyzer.github.io/manual.html#magic-completions

  1. expr.dbg

    let x = 1;
    x.dbg💡
    

    💡 位置でカーソルがある状態でTabキーを押すと以下に変換してくれます。

    let x = 1;
    dbg!(x)
    
  2. expr.dbgr

    let x = 1;
    x.dbgr💡
    

    💡 位置でカーソルがある状態でTabキーを押すと以下に変換してくれます。

    let x = 1;
    dbg!(&x)
    

dbg! マクロの削除

dbg! マクロにカーソルがある状態でCtrl + .またはエディタの左側に表示される 💡 アイコンをクリックすると、
Remove dbg!()メニューが表示され、それをクリックするとdbg!マクロを削除します。

    💡dbg!(1)

Remove dbg!()を実行するとdbg!マクロを削除してくれます。

    1

clippy の利用

rust-analyzer はデフォルトでは保存時にcargo checkを実行をしますが、設定を変更することにより、cargo clippyを実行できます。

VSCode の settings.json の設定例。

{
  // 保存時のコマンドをcargo checkから cargo clippyに変更します。
  "rust-analyzer.checkOnSave.command": "clippy",
  // 保存時のコマンドの引数を指定。ここではdbg!マクロ利用時に警告が出るようにします。
  "rust-analyzer.checkOnSave.extraArgs": ["--", "-W", "clippy::dbg-macro"],
}

Discussion