Implement fallible Rust functions
diff --git a/gen/write.rs b/gen/write.rs
index d57e21e..ccb5a0f 100644
--- a/gen/write.rs
+++ b/gen/write.rs
@@ -271,7 +271,11 @@
}
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
- write_extern_return_type(out, &efn.ret, types);
+ if efn.throws {
+ write!(out, "::rust::Str::Repr ");
+ } else {
+ write_extern_return_type(out, &efn.ret, types);
+ }
for name in out.namespace.clone() {
write!(out, "{}$", name);
}
@@ -305,7 +309,10 @@
write_type_space(out, &arg.ty);
write!(out, "{}", arg.ident);
}
- write!(out, ") noexcept");
+ write!(out, ")");
+ if !efn.throws {
+ write!(out, " noexcept");
+ }
if out.header {
writeln!(out, ";");
} else {
@@ -339,6 +346,9 @@
_ => {}
}
}
+ if efn.throws {
+ write!(out, "::rust::Str::Repr error$ = ");
+ }
for name in out.namespace.clone() {
write!(out, "{}$", name);
}
@@ -374,6 +384,11 @@
}
}
writeln!(out, ";");
+ if efn.throws {
+ writeln!(out, " if (error$.ptr) {{");
+ writeln!(out, " throw ::rust::Error(error$);");
+ writeln!(out, " }}");
+ }
if indirect_return {
writeln!(out, " return ::std::move(return$.value);");
}
@@ -391,7 +406,7 @@
fn indirect_return(efn: &ExternFn, types: &Types) -> bool {
efn.ret
.as_ref()
- .map_or(false, |ret| types.needs_indirect_abi(ret))
+ .map_or(false, |ret| efn.throws || types.needs_indirect_abi(ret))
}
fn write_extern_return_type(out: &mut OutFile, ty: &Option<Type>, types: &Types) {
diff --git a/include/cxx.h b/include/cxx.h
index e02145b..b48abb5 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -138,6 +138,18 @@
};
#endif // CXXBRIDGE02_RUST_BOX
+class Error final : std::exception {
+public:
+ Error(const Error &);
+ Error(Error &&) noexcept;
+ Error(Str::Repr) noexcept;
+ ~Error() noexcept;
+ const char *what() const noexcept override;
+
+private:
+ Str::Repr msg;
+};
+
std::ostream &operator<<(std::ostream &, const String &);
std::ostream &operator<<(std::ostream &, const Str &);
@@ -145,6 +157,7 @@
using string = String;
using str = Str;
template <class T> using box = Box<T>;
+using error = Error;
struct unsafe_bitcopy_t {
explicit unsafe_bitcopy_t() = default;
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index fcfa1d9..defa565 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -136,7 +136,7 @@
});
let ret = expand_extern_return_type(&efn.ret, types);
let mut outparam = None;
- if indirect_return(&efn.ret, types) {
+ if indirect_return(efn, types) {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
}
@@ -154,7 +154,7 @@
let decl = expand_cxx_function_decl(namespace, efn, types);
let args = &efn.args;
let ret = expand_return_type(&efn.ret);
- let indirect_return = indirect_return(&efn.ret, types);
+ let indirect_return = indirect_return(efn, types);
let vars = efn.args.iter().map(|arg| {
let var = &arg.ident;
match &arg.ty {
@@ -267,9 +267,7 @@
}
});
let mut outparam = None;
- let call = quote! {
- ::cxx::private::catch_unwind(__fn, move || super::#ident(#(#vars),*))
- };
+ let call = quote!(super::#ident(#(#vars),*));
let mut expr = efn
.ret
.as_ref()
@@ -289,12 +287,22 @@
_ => None,
})
.unwrap_or(call);
- if indirect_return(&efn.ret, types) {
+ let indirect_return = indirect_return(efn, types);
+ if indirect_return {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
+ }
+ if efn.throws {
+ expr = quote!(::cxx::private::r#try(__return, #expr));
+ } else if indirect_return {
expr = quote!(::std::ptr::write(__return, #expr));
}
- let ret = expand_extern_return_type(&efn.ret, types);
+ expr = quote!(::cxx::private::catch_unwind(__fn, move || #expr));
+ let ret = if efn.throws {
+ quote!(-> ::std::option::Option<::cxx::private::RustStr>)
+ } else {
+ expand_extern_return_type(&efn.ret, types)
+ };
let link_name = format!("{}cxxbridge02${}", namespace, ident);
let local_name = format_ident!("__{}", ident);
let catch_unwind_label = format!("::{}", ident);
@@ -407,9 +415,10 @@
}
}
-fn indirect_return(ret: &Option<Type>, types: &Types) -> bool {
- ret.as_ref()
- .map_or(false, |ret| types.needs_indirect_abi(ret))
+fn indirect_return(efn: &ExternFn, types: &Types) -> bool {
+ efn.ret
+ .as_ref()
+ .map_or(false, |ret| efn.throws || types.needs_indirect_abi(ret))
}
fn expand_extern_type(ty: &Type) -> TokenStream {
diff --git a/src/cxx.cc b/src/cxx.cc
index 6fae0f9..0c1b8af 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -135,6 +135,32 @@
return os;
}
+extern "C" {
+const char *cxxbridge02$error(const char *ptr, size_t len) {
+ char *copy = new char[len];
+ strncpy(copy, ptr, len);
+ return copy;
+}
+} // extern "C"
+
+Error::Error(Str::Repr msg) noexcept : msg(msg) {}
+
+Error::Error(const Error &other) {
+ this->msg.ptr = cxxbridge02$error(other.msg.ptr, other.msg.len);
+ this->msg.len = other.msg.len;
+}
+
+Error::Error(Error &&other) noexcept {
+ delete[] this->msg.ptr;
+ this->msg = other.msg;
+ other.msg.ptr = nullptr;
+ other.msg.len = 0;
+}
+
+Error::~Error() noexcept { delete[] this->msg.ptr; }
+
+const char *Error::what() const noexcept { return this->msg.ptr; }
+
} // namespace cxxbridge02
} // namespace rust
diff --git a/src/exception.rs b/src/exception.rs
new file mode 100644
index 0000000..3f32b45
--- /dev/null
+++ b/src/exception.rs
@@ -0,0 +1,35 @@
+use crate::rust_str::RustStr;
+use std::fmt::Display;
+use std::ptr;
+use std::slice;
+use std::str;
+
+pub unsafe fn r#try<T, E>(ret: *mut T, result: Result<T, E>) -> Option<RustStr>
+where
+ E: Display,
+{
+ match result {
+ Ok(ok) => {
+ ptr::write(ret, ok);
+ None
+ }
+ Err(err) => Some(to_c_string(err.to_string())),
+ }
+}
+
+unsafe fn to_c_string(msg: String) -> RustStr {
+ let mut msg = msg;
+ msg.as_mut_vec().push(b'\0');
+ let ptr = msg.as_ptr();
+ let len = msg.len();
+
+ extern "C" {
+ #[link_name = "cxxbridge02$error"]
+ fn error(ptr: *const u8, len: usize) -> *const u8;
+ }
+
+ let copy = error(ptr, len);
+ let slice = slice::from_raw_parts(copy, len);
+ let string = str::from_utf8_unchecked(slice);
+ RustStr::from(string)
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3744bda..109090c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -354,6 +354,7 @@
mod cxx_string;
mod error;
+mod exception;
mod gen;
mod opaque;
mod paths;
@@ -370,6 +371,7 @@
// Not public API.
#[doc(hidden)]
pub mod private {
+ pub use crate::exception::r#try;
pub use crate::opaque::Opaque;
pub use crate::rust_str::RustStr;
pub use crate::rust_string::RustString;
diff --git a/syntax/check.rs b/syntax/check.rs
index 4c2fff3..b781445 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -75,12 +75,6 @@
"fallible C++ functions are not implemented yet",
));
}
- if efn.throws && efn.lang == Rust {
- errors.push(Error::new_spanned(
- efn,
- "fallible Rust functions are not implemented yet",
- ));
- }
}
_ => {}
}