Skip to content

Commit b11a622

Browse files
committed
Update exception handling to use std::exception_ptr
This makes the interface between C++ exception handling and Rust `Result` cleaner and allows passing C++ exception from the inner C++ call to the outer C++ call via Rust's `CxxException` unmodified, i.e., without losing information. To allow custom exception types, trait `ToCxxException` was introduced. With this trait, it's possible to convert a Rust error to the wrapper `CxxExeception` via a C++ function, thus allowing throwing other exceptions as well. This required changing `r#try` function into macros, so we can properly default to `rust::Error` for errors not having `ToCxxException` defined. Background: The `throw` statement in C++ (__cxa_throw) effectively first allocates space on the heap and creates the exception within, then starts unwinding. This can be also done via standard C++11 API in two steps. First, `std::make_exception_ptr()` creates a new `std::exception_ptr`, which points into this allocated space and internally is just a pointer (it's a smart pointer much like `std::shared_ptr`). Then, `std::rethrow_exception()` can be used to actually throw and/or rethrow this exception. Basically, the new implementation now uses `std::make_exception_ptr()` called from Rust to construct an exception for the `Result<_, E>` and then after returning it back to C++ via `CxxResult` (which is now BTW smaller, just 8B) the C++ part throws it using `std::rethrow_exception()`.
1 parent 73eeef3 commit b11a622

File tree

8 files changed

+275
-97
lines changed

8 files changed

+275
-97
lines changed

Diff for: book/src/binding/result.md

+25-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@ the Rust side produces an error.
2525
Note that the return type written inside of cxx::bridge must be written without
2626
a second type parameter. Only the Ok type is specified for the purpose of the
2727
FFI. The Rust *implementation* (outside of the bridge module) may pick any error
28-
type as long as it has a std::fmt::Display impl.
28+
type as long as it has a `std::fmt::Display` or `cxx:ToCxxException`
29+
implementation.
30+
31+
Exception is built from the actual error type via `cxx::ToCxxException` trait
32+
which converts the error type into a custom exception by the user code, if such
33+
an implementation exists, else using `cxx::ToCxxExceptionDefault`, which only
34+
requires the type to implement `std::fmt::Display` trait. The sole trait method
35+
of both traits returns a `cxx::CxxException`, which wraps a `std::exception_ptr`
36+
on the C++ side. An implementation of `cxx::ToCxxException` will call the
37+
appropriate C++ function (again, via the bridge) to construct the
38+
`std::exception_ptr`, likely using standard C++ function
39+
`std::make_exception_ptr()` to wrap an exception. The signature on the C++ side
40+
expects `std::exception_ptr` for `cxx::CxxException` on the Rust side.
2941

3042
```rust,noplayground
3143
# use std::io;
@@ -51,9 +63,10 @@ fn fallible2() -> Result<(), io::Error> {
5163
}
5264
```
5365

54-
The exception that gets thrown by CXX on the C++ side is always of type
55-
`rust::Error` and has the following C++ public API. The `what()` member function
56-
gives the error message according to the Rust error's std::fmt::Display impl.
66+
The exception that gets thrown by CXX on the C++ side is of type `rust::Error`
67+
(unless otherwise specified by `cxx::ToCxxException` trait for a custom error
68+
type) and has the following C++ public API. The `what()` member function gives
69+
the error message according to the Rust error's `std::fmt::Display` implementation.
5770

5871
```cpp,hidelines
5972
// rust/cxx.h
@@ -85,6 +98,12 @@ a second type parameter. Only the Ok type is specified for the purpose of the
8598
FFI. The resulting error type created by CXX when an `extern "C++"` function
8699
throws will always be of type **[`cxx::Exception`]**.
87100

101+
Note that this exception can be converted to [`cxx::CxxException`] using its
102+
`Into` trait implementation and returned back to C++ later, as a `Result` with
103+
error type `CxxException`, providing a transparent bridge from the original C++
104+
exception thrown in a C++ callback through Rust API back to the C++ code calling
105+
the Rust API without loss of information.
106+
88107
[`cxx::Exception`]: https://docs.rs/cxx/*/cxx/struct.Exception.html
89108

90109
```rust,noplayground
@@ -141,6 +160,8 @@ static void trycatch(Try &&func, Fail &&fail) noexcept try {
141160
func();
142161
} catch (const std::exception &e) {
143162
fail(e.what());
163+
} catch (...) {
164+
fail("<no message>");
144165
}
145166
#
146167
# } // namespace behavior

Diff for: gen/src/builtin.rs

+15-19
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct Builtins<'a> {
2020
pub manually_drop: bool,
2121
pub maybe_uninit: bool,
2222
pub trycatch: bool,
23-
pub ptr_len: bool,
23+
pub repr_cxxresult: bool,
2424
pub repr_fat: bool,
2525
pub rust_str_new_unchecked: bool,
2626
pub rust_str_repr: bool,
@@ -138,7 +138,7 @@ pub(super) fn write(out: &mut OutFile) {
138138
}
139139

140140
if builtin.trycatch {
141-
builtin.ptr_len = true;
141+
builtin.repr_cxxresult = true;
142142
}
143143

144144
out.begin_block(Block::Namespace("rust"));
@@ -217,13 +217,21 @@ pub(super) fn write(out: &mut OutFile) {
217217
writeln!(out, "using Fat = ::std::array<::std::uintptr_t, 2>;");
218218
}
219219

220-
if builtin.ptr_len {
220+
if builtin.repr_cxxresult {
221+
include.exception = true;
221222
include.cstddef = true;
222223
out.next_section();
223224
writeln!(out, "struct PtrLen final {{");
224225
writeln!(out, " void *ptr;");
225226
writeln!(out, " ::std::size_t len;");
226227
writeln!(out, "}};");
228+
writeln!(out, "struct CxxResult final {{");
229+
writeln!(out, " void *ptr;");
230+
writeln!(out, "}};");
231+
writeln!(out, "struct Exception final {{");
232+
writeln!(out, " CxxResult exc;");
233+
writeln!(out, " PtrLen msg;");
234+
writeln!(out, "}};");
227235
}
228236

229237
out.end_block(Block::Namespace("repr"));
@@ -258,11 +266,11 @@ pub(super) fn write(out: &mut OutFile) {
258266
include.string = true;
259267
out.next_section();
260268
writeln!(out, "class Fail final {{");
261-
writeln!(out, " ::rust::repr::PtrLen &throw$;");
269+
writeln!(out, " ::rust::repr::Exception &throw$;");
262270
writeln!(out, "public:");
263271
writeln!(
264272
out,
265-
" Fail(::rust::repr::PtrLen &throw$) noexcept : throw$(throw$) {{}}",
273+
" Fail(::rust::repr::Exception &throw$) noexcept : throw$(throw$) {{}}",
266274
);
267275
writeln!(out, " void operator()(char const *) noexcept;");
268276
writeln!(out, " void operator()(std::string const &) noexcept;");
@@ -345,20 +353,6 @@ pub(super) fn write(out: &mut OutFile) {
345353
writeln!(out, "}};");
346354
}
347355

348-
if builtin.rust_error {
349-
out.next_section();
350-
writeln!(out, "template <>");
351-
writeln!(out, "class impl<Error> final {{");
352-
writeln!(out, "public:");
353-
writeln!(out, " static Error error(repr::PtrLen repr) noexcept {{");
354-
writeln!(out, " Error error;");
355-
writeln!(out, " error.msg = static_cast<char const *>(repr.ptr);");
356-
writeln!(out, " error.len = repr.len;");
357-
writeln!(out, " return error;");
358-
writeln!(out, " }}");
359-
writeln!(out, "}};");
360-
}
361-
362356
if builtin.destroy {
363357
out.next_section();
364358
writeln!(out, "template <typename T>");
@@ -414,6 +408,8 @@ pub(super) fn write(out: &mut OutFile) {
414408
writeln!(out, " func();");
415409
writeln!(out, "}} catch (::std::exception const &e) {{");
416410
writeln!(out, " fail(e.what());");
411+
writeln!(out, "}} catch (...) {{");
412+
writeln!(out, " fail(\"<no message>\");");
417413
writeln!(out, "}}");
418414
out.end_block(Block::Namespace("behavior"));
419415
}

Diff for: gen/src/write.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -706,8 +706,8 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
706706
out.begin_block(Block::ExternC);
707707
begin_function_definition(out);
708708
if efn.throws {
709-
out.builtin.ptr_len = true;
710-
write!(out, "::rust::repr::PtrLen ");
709+
out.builtin.repr_cxxresult = true;
710+
write!(out, "::rust::repr::Exception ");
711711
} else {
712712
write_extern_return_type_space(out, &efn.ret);
713713
}
@@ -783,9 +783,9 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
783783
writeln!(out, ";");
784784
write!(out, " ");
785785
if efn.throws {
786-
out.builtin.ptr_len = true;
786+
out.builtin.repr_cxxresult = true;
787787
out.builtin.trycatch = true;
788-
writeln!(out, "::rust::repr::PtrLen throw$;");
788+
writeln!(out, "::rust::repr::Exception throw$;");
789789
writeln!(out, " ::rust::behavior::trycatch(");
790790
writeln!(out, " [&] {{");
791791
write!(out, " ");
@@ -856,7 +856,8 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
856856
}
857857
writeln!(out, ";");
858858
if efn.throws {
859-
writeln!(out, " throw$.ptr = nullptr;");
859+
writeln!(out, " throw$.exc.ptr = nullptr;");
860+
writeln!(out, " throw$.msg.ptr = nullptr;");
860861
writeln!(out, " }},");
861862
writeln!(out, " ::rust::detail::Fail(throw$));");
862863
writeln!(out, " return throw$;");
@@ -899,8 +900,8 @@ fn write_rust_function_decl_impl(
899900
) {
900901
out.next_section();
901902
if sig.throws {
902-
out.builtin.ptr_len = true;
903-
write!(out, "::rust::repr::PtrLen ");
903+
out.builtin.repr_cxxresult = true;
904+
write!(out, "::rust::repr::CxxResult ");
904905
} else {
905906
write_extern_return_type_space(out, &sig.ret);
906907
}
@@ -1074,8 +1075,8 @@ fn write_rust_function_shim_impl(
10741075
}
10751076
}
10761077
if sig.throws {
1077-
out.builtin.ptr_len = true;
1078-
write!(out, "::rust::repr::PtrLen error$ = ");
1078+
out.builtin.repr_cxxresult = true;
1079+
write!(out, "::rust::repr::CxxResult error$ = ");
10791080
}
10801081
write!(out, "{}(", invoke);
10811082
let mut needs_comma = false;
@@ -1124,7 +1125,8 @@ fn write_rust_function_shim_impl(
11241125
if sig.throws {
11251126
out.builtin.rust_error = true;
11261127
writeln!(out, " if (error$.ptr) {{");
1127-
writeln!(out, " throw ::rust::impl<::rust::Error>::error(error$);");
1128+
writeln!(out, " void *ppexc = &error$.ptr;");
1129+
writeln!(out, " std::rethrow_exception(std::move(*static_cast<std::exception_ptr*>(ppexc)));");
11281130
writeln!(out, " }}");
11291131
}
11301132
if indirect_return {

Diff for: macro/src/expand.rs

+21-10
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
146146
clippy::ptr_as_ptr,
147147
clippy::upper_case_acronyms,
148148
clippy::use_self,
149+
clippy::items_after_statements,
149150
)]
150151
#vis #mod_token #ident #expanded
151152
}
@@ -471,7 +472,7 @@ fn expand_cxx_function_decl(efn: &ExternFn, types: &Types) -> TokenStream {
471472
});
472473
let all_args = receiver.chain(args);
473474
let ret = if efn.throws {
474-
quote!(-> ::cxx::private::Result)
475+
quote!(-> ::cxx::private::CxxResultWithMessage)
475476
} else {
476477
expand_extern_return_type(&efn.ret, types, true)
477478
};
@@ -1108,7 +1109,7 @@ fn expand_rust_function_shim_impl(
11081109
None => quote_spanned!(span=> &mut ()),
11091110
};
11101111
requires_closure = true;
1111-
expr = quote_spanned!(span=> ::cxx::private::r#try(#out, #expr));
1112+
expr = quote_spanned!(span=> ::cxx::map_rust_result_to_cxx_result!(#out, #expr));
11121113
} else if indirect_return {
11131114
requires_closure = true;
11141115
expr = quote_spanned!(span=> ::cxx::core::ptr::write(__return, #expr));
@@ -1123,7 +1124,7 @@ fn expand_rust_function_shim_impl(
11231124
expr = quote_spanned!(span=> ::cxx::private::prevent_unwind(__fn, #closure));
11241125

11251126
let ret = if sig.throws {
1126-
quote!(-> ::cxx::private::Result)
1127+
quote!(-> ::cxx::private::CxxResult)
11271128
} else {
11281129
expand_extern_return_type(&sig.ret, types, false)
11291130
};
@@ -1166,16 +1167,12 @@ fn expand_rust_function_shim_super(
11661167
let args = sig.args.iter().map(|arg| quote!(#arg));
11671168
let all_args = receiver.chain(args);
11681169

1169-
let ret = if let Some((result, _langle, rangle)) = sig.throws_tokens {
1170+
let ret = if let Some((result, _langle, _rangle)) = &sig.throws_tokens {
11701171
let ok = match &sig.ret {
11711172
Some(ret) => quote!(#ret),
11721173
None => quote!(()),
11731174
};
1174-
// Set spans that result in the `Result<...>` written by the user being
1175-
// highlighted as the cause if their error type has no Display impl.
1176-
let result_begin = quote_spanned!(result.span=> ::cxx::core::result::Result<#ok, impl);
1177-
let result_end = quote_spanned!(rangle.span=> ::cxx::core::fmt::Display>);
1178-
quote!(-> #result_begin #result_end)
1175+
quote_spanned!(result.span=> -> ::cxx::core::result::Result<#ok, ::cxx::CxxException>)
11791176
} else {
11801177
expand_return_type(&sig.ret)
11811178
};
@@ -1192,9 +1189,23 @@ fn expand_rust_function_shim_super(
11921189
}
11931190
};
11941191

1192+
let call = if let Some((result, _langle, rangle)) = &sig.throws_tokens {
1193+
// Set spans that result in the `Result<...>` written by the user being
1194+
// highlighted as the cause if their error type is not convertible to
1195+
// CxxException (i.e., no `Display` trait by default).
1196+
let result_begin = quote_spanned!{ result.span=>
1197+
|e| ::cxx::map_rust_error_to_cxx_exception!
1198+
};
1199+
let result_end = quote_spanned!{ rangle.span=> (e) };
1200+
quote_spanned! {span=>
1201+
#call(#(#vars,)*).map_err( #result_begin #result_end )
1202+
}
1203+
} else {
1204+
quote_spanned! {span=> #call(#(#vars,)*) }
1205+
};
11951206
quote_spanned! {span=>
11961207
#unsafety fn #local_name #generics(#(#all_args,)*) #ret {
1197-
#call(#(#vars,)*)
1208+
#call
11981209
}
11991210
}
12001211
}

Diff for: src/cxx.cc

+20-10
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,6 @@ static_assert(alignof(std::exception_ptr) == alignof(void *),
474474
"Unsupported std::exception_ptr alignment");
475475

476476
extern "C" {
477-
const char *cxxbridge1$error(const char *ptr, std::size_t len) noexcept {
478-
return errorCopy(ptr, len);
479-
}
480-
481477
void *cxxbridge1$default_exception(const char *ptr, std::size_t len) noexcept {
482478
// Construct an `std::exception_ptr` for the default `rust::Error` in the
483479
// space provided by the pointer itself (placement new).
@@ -501,8 +497,9 @@ void *cxxbridge1$clone_exception(const char *ptr) noexcept {
501497
// Implement the `clone` for `CxxException` on the Rust side, which is just a
502498
// pointer to the exception stored in `std::exception_ptr`.
503499
void *eptr;
504-
new (&eptr) std::exception_ptr(
505-
*reinterpret_cast<const std::exception_ptr *const>(&ptr));
500+
const void *pptr = &ptr;
501+
new (&eptr)
502+
std::exception_ptr(*static_cast<const std::exception_ptr *>(pptr));
506503
return eptr;
507504
}
508505
} // extern "C"
@@ -559,6 +556,13 @@ struct PtrLen final {
559556
void *ptr;
560557
std::size_t len;
561558
};
559+
struct CxxResult final {
560+
void *ptr;
561+
};
562+
struct Exception final {
563+
CxxResult exc;
564+
PtrLen msg;
565+
};
562566
} // namespace repr
563567

564568
extern "C" {
@@ -580,20 +584,26 @@ using isize_if_unique =
580584
struct isize_ignore, rust::isize>::type;
581585

582586
class Fail final {
583-
repr::PtrLen &throw$;
587+
repr::Exception &throw$;
584588

585589
public:
586-
Fail(repr::PtrLen &throw$) noexcept : throw$(throw$) {}
590+
Fail(repr::Exception &throw$) noexcept : throw$(throw$) {}
587591
void operator()(const char *) noexcept;
588592
void operator()(const std::string &) noexcept;
589593
};
590594

591595
void Fail::operator()(const char *catch$) noexcept {
592-
throw$ = cxxbridge1$exception(catch$, std::strlen(catch$));
596+
void *eptr;
597+
new (&eptr)::std::exception_ptr(::std::current_exception());
598+
throw$.exc.ptr = eptr;
599+
throw$.msg = cxxbridge1$exception(catch$, std::strlen(catch$));
593600
}
594601

595602
void Fail::operator()(const std::string &catch$) noexcept {
596-
throw$ = cxxbridge1$exception(catch$.data(), catch$.length());
603+
void *eptr;
604+
new (&eptr)::std::exception_ptr(::std::current_exception());
605+
throw$.exc.ptr = eptr;
606+
throw$.msg = cxxbridge1$exception(catch$.data(), catch$.length());
597607
}
598608
} // namespace detail
599609

Diff for: src/exception.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#![cfg(feature = "alloc")]
22

33
use alloc::boxed::Box;
4-
use core::fmt::{self, Display};
4+
use core::fmt::{self, Display, Debug};
5+
6+
use crate::CxxException;
57

68
/// Exception thrown from an `extern "C++"` function.
79
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
8-
#[derive(Debug)]
910
pub struct Exception {
11+
pub(crate) src: CxxException,
1012
pub(crate) what: Box<str>,
1113
}
1214

@@ -16,6 +18,12 @@ impl Display for Exception {
1618
}
1719
}
1820

21+
impl Debug for Exception {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
f.debug_struct("Exception").field("what", &self.what).finish()
24+
}
25+
}
26+
1927
#[cfg(feature = "std")]
2028
#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
2129
impl std::error::Error for Exception {}

0 commit comments

Comments
 (0)