私は std::backtrace::Backtrace がなんとなく1嫌で、その「なんとなく」だけで8カ月ほど時間をかけて hooq という属性マクロを作ってしまいました...
このままでは良くないので、ちゃんと触っておきたいと思います。というわけで、 std::backtrace::Backtrace に関する諸々をまとめました!
Backtrace の使い方はシンプル!
std::backtraceを読んだ感じ、Backtrace 自体はすごくシンプルで特にエラー等とは関係なく、シンプルにスタックバックトレースを得る機能のようです。
モジュールトップに説明が書かれていますが特に知っておくべき情報は以下ぐらいでした2:
- OSスレッドに依存
- プラットフォームによってはキャプチャできない
- 利用可能かは
Backtrace::statusメソッドで確認できる
- 基本的には、環境変数でキャプチャされるかを管理
-
Backtrace::force_captureは無関係にキャプチャ -
Backtrace::captureは以下に従ってキャプチャを決定-
RUST_LIB_BACKTRACEが0ならキャプチャしない。そのほかの値 (1とかfullとか任意) ならキャプチャ -
RUST_LIB_BACKTRACEが設定されていない時、RUST_BACKTRACE変数で上記と同様の判定 - 両方とも設定されていない時、キャプチャは行わない
-
-
使用例は記載されていませんでしたが、シンプルな例は次のような感じでしょう。stableで使える主要APIについては以下のソースコードで大体網羅しています。
use std::backtrace::Backtrace;
fn func1() -> Backtrace {
Backtrace::capture()
}
fn func2() -> Backtrace {
func1()
}
fn func3() -> Backtrace {
func2()
}
fn main() {
let bt = func3();
println!("Status: {:?}\n---\n{}", bt.status(), bt);
}
$ RUST_BACKTRACE=1 cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/backtrace_pg`
Status: Captured
---
0: backtrace_pg::func1
at ./src/main.rs:4:5
1: backtrace_pg::func2
at ./src/main.rs:8:5
2: backtrace_pg::func3
at ./src/main.rs:12:5
3: backtrace_pg::main
at ./src/main.rs:16:14
... 以降省略 ...
出力例省略なし
$ RUST_BACKTRACE=1 cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/backtrace_pg`
Status: Captured
---
0: backtrace_pg::func1
at ./src/main.rs:4:5
1: backtrace_pg::func2
at ./src/main.rs:8:5
2: backtrace_pg::func3
at ./src/main.rs:12:5
3: backtrace_pg::main
at ./src/main.rs:16:14
4: core::ops::function::FnOnce::call_once
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
5: std::sys::backtrace::__rust_begin_short_backtrace
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
6: std::rt::lang_start::{{closure}}
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
7: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/core/src/ops/function.rs:287:21
8: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
9: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
10: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
11: std::rt::lang_start_internal::{{closure}}
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:175:24
12: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
13: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
14: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
15: std::rt::lang_start_internal
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:171:5
16: std::rt::lang_start
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
17: main
18: <unknown>
19: __libc_start_main
20: _start
パニック時に得られるバックトレースと似た出力が得られましたね。有益な情報はトレース序盤の最初の行の方に固まっていそうです。
おまけ: nightlyで使えるBacktrace::framesも試してみた
#![feature(backtrace_frames)] を入れてnightly付きで実行すると Backtrace::frames というメソッドにアクセスできます。
名前通りバックトレースのフレーム(0: backtrace_pg::func1 at ./src/main.rs:4:5 みたいな単位)をスライスで得られるようです。
#![feature(backtrace_frames)]
use std::backtrace::Backtrace;
fn func1() -> Backtrace {
Backtrace::capture()
}
fn func2() -> Backtrace {
func1()
}
fn func3() -> Backtrace {
func2()
}
fn main() {
let bt = func3();
println!("Status: {:?}\n---\n{}", bt.status(), bt);
for (i, frame) in bt.frames().iter().enumerate() {
println!("Frame {}: {:?}", i, frame);
}
}
出力例
$ RUST_BACTRACE=1 cargo +nightly run
Compiling backtrace_frames_pg v0.1.0 (/home/namn/workspace/qiita_adv_articles_2025/programs/adv18/backtrace_frames_pg)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
Running `target/debug/backtrace_frames_pg`
Status: Disabled
---
disabled backtrace
programs/adv18/backtrace_frames_pg on main [!?] is 📦 v0.1.0 via 🦀 v1.91.1
❯ RUST_BACKTRACE=1 cargo +nightly run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/backtrace_frames_pg`
Status: Captured
---
0: backtrace_frames_pg::func1
at ./src/main.rs:6:5
1: backtrace_frames_pg::func2
at ./src/main.rs:10:5
2: backtrace_frames_pg::func3
at ./src/main.rs:14:5
3: backtrace_frames_pg::main
at ./src/main.rs:18:14
4: <fn() as core::ops::function::FnOnce<()>>::call_once
at /home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
5: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
at /home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
6: std::rt::lang_start::<()>::{closure#0}
at /home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
7: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/core/src/ops/function.rs:287:21
8: std::panicking::catch_unwind::do_call
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:582:40
9: std::panicking::catch_unwind
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:545:19
10: std::panic::catch_unwind
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs:359:14
11: std::rt::lang_start_internal::{{closure}}
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs:175:24
12: std::panicking::catch_unwind::do_call
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:582:40
13: std::panicking::catch_unwind
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:545:19
14: std::panic::catch_unwind
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs:359:14
15: std::rt::lang_start_internal
at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs:171:5
16: std::rt::lang_start::<()>
at /home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
17: main
18: <unknown>
19: __libc_start_main
20: _start
Frame 0: [{ fn: "std::backtrace_rs::backtrace::libunwind::trace", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/../../backtrace/src/backtrace/libunwind.rs", line: 117 }, { fn: "std::backtrace_rs::backtrace::trace_unsynchronized", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/../../backtrace/src/backtrace/mod.rs", line: 66 }, { fn: "std::backtrace::Backtrace::create", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/backtrace.rs", line: 331 }]
Frame 1: [{ fn: "std::backtrace::Backtrace::capture", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/backtrace.rs", line: 296 }]
Frame 2: [{ fn: "backtrace_frames_pg::func1", file: "./src/main.rs", line: 6 }]
Frame 3: [{ fn: "backtrace_frames_pg::func2", file: "./src/main.rs", line: 10 }]
Frame 4: [{ fn: "backtrace_frames_pg::func3", file: "./src/main.rs", line: 14 }]
Frame 5: [{ fn: "backtrace_frames_pg::main", file: "./src/main.rs", line: 18 }]
Frame 6: [{ fn: "<fn() as core::ops::function::FnOnce<()>>::call_once", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs", line: 250 }]
Frame 7: [{ fn: "std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs", line: 158 }]
Frame 8: [{ fn: "std::rt::lang_start::<()>::{closure#0}", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs", line: 206 }]
Frame 9: [{ fn: "core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/core/src/ops/function.rs", line: 287 }, { fn: "std::panicking::catch_unwind::do_call", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 582 }, { fn: "std::panicking::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 545 }, { fn: "std::panic::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs", line: 359 }, { fn: "std::rt::lang_start_internal::{{closure}}", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs", line: 175 }, { fn: "std::panicking::catch_unwind::do_call", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 582 }, { fn: "std::panicking::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 545 }, { fn: "std::panic::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs", line: 359 }, { fn: "std::rt::lang_start_internal", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs", line: 171 }]
Frame 10: [{ fn: "std::rt::lang_start::<()>", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs", line: 205 }]
Frame 11: [{ fn: "main" }]
Frame 12: []
Frame 13: [{ fn: "__libc_start_main" }]
Frame 14: [{ fn: "_start" }]
Frame 15: []
ただBacktraceFrameはデバッグ出力以外の能力がないようなので需要は不明です。
せめてファイル名や行番号のAPIは晒してほしい...
なんでバックトレースを管理する環境変数が2つあるの?
ドキュメントには書いておらず筆者もChatGPTに言われて理解できたのですが、以下のモチベがあるようです。
- モチベ1: 「パニック時のバックトレース出力」と「
std::backtrace::Backtrace由来のバックトレース出力」の制御を別々にしたい - モチベ2: とはいえ両方出力したい時に両方指定するのは面倒
パニック時のバックトレース出力は RUST_BACKTRACE のみに依存し後付けされた RUST_LIB_BACKTRACE には影響されないことを利用し、以下のように出力を制御できます。
- パニック時も
Backtraceでも出力したい:RUST_BACKTRACE=1 -
Backtrace分だけ出力しパニック時は出力しない:RUST_LIB_BACKTRACE=1 - パニック時だけ出力し
Backtrace分は出力しない:RUST_LIB_BACKTRACE=0&RUST_BACKTRACE=1
なるほどこれなら細かいニーズに対応できそうです!
まとめ・所感
というわけで、 hooqアドベントカレンダー 18日目の記事でした!
色々調べたのですが、記事に起こすのは間に合わなかったので明日も引き続きBacktraceについてまとめたいと思います(まとめました)。多分hooqアドベントカレンダー中で一番内容が薄い記事だったかも ![]()
ともかく、ここまで読んでいただきありがとうございました!
-
具体的には、表示が見辛い(color-eyreで改善)、比較的重い処理らしくそのせいでいちいち環境変数を用意するのが煩雑、非同期だとうまく機能しなさそう、といった理由ですが、ちゃんと検証したわけではないので、「なんとなく」です。 ↩
-
他の「正確性」や「プラットフォームサポート」はFYIではありますが本筋とは関係なさそうなので本記事ではまとめません。興味がある方は読んでみるとよさそうです。 ↩