Rustを経由してC++例外を補足する
Rust 1.71.0でC-unwind
ABIが安定化されました
これによりFFI境界を越えて処理を巻き戻せるようになります。例えばC++からRustの関数を呼び出し、RustからさらにC++の関数を呼び出しているとき、一番内側のC++関数が例外を投げると従来はそれをRustの層を越えて元のC++側に伝搬させる事が出来ませんでしたが、1.71.0からは可能になります。
今回実験したコードは以下にあります。
まずRust側から見ていきましょう。
#[no_mangle]
extern "C-unwind" fn rust_middle_func(callback: extern "C-unwind" fn()) {
let _a = A {}; // test destructor
callback(); // This raises C++ exception
// Following will not be executed
unreachable!("End of rust_middle_func");
}
C++側からrust_middle_func
をFFIで呼び出し、さらに引数にコールバック関数を渡します。rust_middle_func
は受け取ったcallback
を呼ぶだけですが、Drop
のテストのために一つ構造体を用意します。
struct A {}
impl Drop for A {
fn drop(&mut self) {
println!("Drop A in Rust");
}
}
Rustがcallback
を呼び出してそれが例外を投げたとき、呼び出したRust側での処理も中断してしまうので、確保した構造体(この場合_a
)の後片付けをする必要があります。
次にC++の方を見てみます。
extern "C" {
void throw_cxx_exception() {
std::cout << "Throwing C++ exception" << std::endl;
throw std::runtime_error("C++ Runtime error");
}
void rust_middle_func(void (*callback)());
void test_cxx_exception() {
try {
rust_middle_func(throw_cxx_exception);
std::cout << "End of C++ try block" << std::endl;
} catch (const std::exception &e) {
std::cout << "Catched: " << e.what() << std::endl;
return;
}
}
}
Try-cache構文を使って、例外を投げるだけの関数throw_cxx_exception
をFFIでrust_middle_func
に渡します。Cargoを使いたいのでtest_cxx_exception
はRust側から使います。
extern "C" {
fn test_cxx_exception();
}
fn main() {
unsafe {
test_cxx_exception();
}
}
Cargoでリンクするのでcc crateを使います。
fn main() {
cc::Build::new()
.cpp(true)
.file("foo.cpp")
.cpp_link_stdlib("stdc++") // use libstdc++
.compile("foo");
}
これで準備が出来ました。
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/cpp-exception-testing`
Throwing C++ exception
Drop A in Rust
Catched: C++ Runtime error
この様にC++の例外がRustの層を跨いでやりとり出来ており、しかも途中でRust側のDrop
もちゃんと呼ばれています。
panic=abort
以上はpanic=unwind
の時の挙動で
[profile.dev-abort]
inherits = "dev"
panic = "abort"
のようにpanic=abort
を指定した場合は
$ cargo run --profile=dev-abort
Finished dev-abort [unoptimized + debuginfo] target(s) in 0.00s
Running `target/dev-abort/cpp-exception-testing`
Throwing C++ exception
thread 'main' panicked at 'panic in a function that cannot unwind', library/core/src/panicking.rs:126:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.
の様にRustの層でC++の例外を検知した段階でabortするようです。
RustでC++の例外を受け取る
Also note that unwinding into Rust code with a foreign exception (e.g. an exception thrown from C++ code) is undefined behavior.
https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
のは出来ません(´・ω・`)
Discussion