なぜRustをデバッグする方法を知るべきか
Stackoverflowの2020年の調査によればRustは2020年時点でもっとも愛されている言語でありC/C++並みの速度、あるいはより速い速度を持つのにメモリの安全性が保証されているとかコンパイラが賢いとか色々いい感じの言語です。でも、いい言語なのですが一般的に学習するのが難しいという評価がされています。そんなRustですが色々なデバッグ方法を知っていれば難易度はいくらか下がると思います。なのでここにデバッグ方法を色々まとめたいと思います。
といいつつ私もまだRustを始めたばかりで知らないことが多いのでなにか間違っていたらご指摘お願いします…。Rust playgroundで実際にコードを試しながら読むとわかりやすいと思います!
Printデバッグ
まずはおなじみのPrintデバッグです。println!("{:?}", 変数など);で中身を確認することができます。
pub fn main() {
let test = vec![100, 101, 102, 103];
println!("{:?}", test);
}
実行結果
[100, 101, 102, 103]
structに対して{:?}で情報をprintln!したい場合はdebugというattributeを追加しましょう。
pub fn main() {
let test = Person { name: "test", age: 20 };
println!("{:?}", test);
}
#[derive(Debug)] // これを追加
struct Person<'a> {
name: &'a str,
age: u8
}
実行結果
Person { name: "test", age: 20 }
enumも同じです。
pub fn main() {
let test = Fruits::Apple("ringo".to_string());
println!("{:?}", test);
}
#[derive(Debug)] // これを追加
enum Fruits {
Apple(String),
Grape(String),
Orange(String)
}
実行結果
Apple("ringo")
dbg!マクロ
Rust1.32.0からdbg!マクロが使用でき、その処理の含まれるファイル名・何行目の処理なのかを表示できます。
pub fn main() {
let people = [
Person { name: "test1", age: 20 },
Person { name: "test2", age: 25 },
Person { name: "test3", age: 30 },
];
dbg!(&people);
}
#[derive(Debug)]
struct Person<'a> {
name: &'a str,
age: u8
}
実行結果
[src/main.rs:7] &people = [
Person {
name: "test1",
age: 20,
},
Person {
name: "test2",
age: 25,
},
Person {
name: "test3",
age: 30,
},
]
デバッグビルド時にのみ情報をprintする
stackoverflowの回答者がdebugビルド時にのみデバッグ情報をprintするマクロを回答してくれていました。以下stackoverflowからそのままコピペですが、debug!はreleaseビルド時にはデバッグの情報をprintしません。Playgroundで試す場合はこちら。
#[cfg(debug_assertions)]
macro_rules! debug {
($x:expr) => { dbg!($x) }
}
#[cfg(not(debug_assertions))]
macro_rules! debug {
($x:expr) => { std::convert::identity($x) }
}
fn main() {
let x = 4;
debug!(x);
if debug!(x == 5) {
println!("x == 5");
} else {
println!("x != 5");
}
}
2020/11/8 追記
@benkiさんのコメントによると以下のような書き方でもデバッグビルド時のみデバッグ情報を表示することができるようです。情報提供ありがとうございます!!
if cfg!(debug_assertions) {
dbg!(hoge);
}
VSCodeでステップ実行
Rustはvscodeでステップ実行できます。以下、情報元のstackoverflowからその方法をそのまま和訳します。
1. VS Codeのインストール
2. 「Rust」もしくは「rust-analyzer」をVSCodeのExtensionsで検索してVSCodeに追加
3. 「CodeLLDB」をVSCodeのExtensionsで検索してVSCodeに追加
4. VSCodeの左のデバッグアイコンを押す→launch.jsonを追加を選択 すると「LLDB」がデバッグ対象の選択肢に追加されているのでこれを選びます。
5. LLDBを選択すると.vscodeフォルダのなかにデバッグ設定用のjsonファイルが追加されます。
6. このjsonファイルを(メモ帳でもなんでもいいので)開き、中身を以下のように書き換えます。
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Rust Debug Launch",
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
"args": [],
"cwd": "${workspaceRoot}/target/debug/",
"sourceLanguages": ["rust"]
}
]
}
参考に私のプロジェクト (Qiitaの記事) の場合、以下のような設定にしています。argsに実行時の引数を追加していますね。
{
"version": "0.2.0",
"configurations": [
{
"name": "Rust Debug Launch",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/target/debug/${workspaceRootFolderName}",
"args": ["--file", "${workspaceRoot}/examples/test.orn"],
"cwd": "${workspaceRoot}/target/debug/",
"sourceLanguages": ["rust"]
},
],
}
Cargo.tomlに以下を追加すると表示されるデバッグ情報がより正確になるかもしれません。
あくまでもデバッグのためのものであって、リリースビルド時には消したほうがいいでしょう。
[profile.dev]
opt-level = 0
デバッグを開始するまえに、rustのコードはあらかじめbuildしておきます。
$ cargo build
あとはこんな感じでブレークポイントを設定して(行数が書いてあるところのちょっと左をクリックする)、デバッグメニューのところから「Rust Debug Launch」を選択、隣りにあるRunを押して実行すればブレークポイントで処理を止めることができます。
2020/11/8 追記
有料らしいですが、CLionとIntellij idea ultimateもdebugできるそうです。
https://github.com/intellij-rust/intellij-rust
@_gz23_さん、情報の提供ありがとうございます!!