Implement fallible C++ functions
diff --git a/gen/include.rs b/gen/include.rs
index a4b416a..da6678d 100644
--- a/gen/include.rs
+++ b/gen/include.rs
@@ -18,6 +18,7 @@
pub struct Includes {
custom: Vec<String>,
pub cstdint: bool,
+ pub cstring: bool,
pub memory: bool,
pub string: bool,
pub type_traits: bool,
@@ -41,6 +42,9 @@
if self.cstdint {
writeln!(f, "#include <cstdint>")?;
}
+ if self.cstring {
+ writeln!(f, "#include <cstring>")?;
+ }
if self.memory {
writeln!(f, "#include <memory>")?;
}
diff --git a/gen/write.rs b/gen/write.rs
index ccb5a0f..3398f9a 100644
--- a/gen/write.rs
+++ b/gen/write.rs
@@ -45,6 +45,7 @@
if !header {
out.begin_block("extern \"C\"");
+ write_exception_glue(out, apis);
for api in apis {
let (efn, write): (_, fn(_, _, _)) = match api {
Api::CxxFunction(efn) => (efn, write_cxx_function_shim),
@@ -186,8 +187,32 @@
writeln!(out, "using {} = {};", ident, ident);
}
+fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
+ let mut has_cxx_throws = false;
+ for api in apis {
+ if let Api::CxxFunction(efn) = api {
+ if efn.throws {
+ has_cxx_throws = true;
+ break;
+ }
+ }
+ }
+
+ if has_cxx_throws {
+ out.next_section();
+ write!(
+ out,
+ "const char *cxxbridge02$exception(const char *, size_t);",
+ );
+ }
+}
+
fn write_cxx_function_shim(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);
}
@@ -221,6 +246,11 @@
}
writeln!(out, ") = {};", efn.ident);
write!(out, " ");
+ if efn.throws {
+ writeln!(out, "::rust::Str::Repr throw$;");
+ writeln!(out, " try {{");
+ write!(out, " ");
+ }
if indirect_return {
write!(out, "new (return$) ");
write_type(out, efn.ret.as_ref().unwrap());
@@ -267,6 +297,19 @@
write!(out, ")");
}
writeln!(out, ";");
+ if efn.throws {
+ out.include.cstring = true;
+ writeln!(out, " throw$.ptr = nullptr;");
+ writeln!(out, " }} catch (const ::std::exception &catch$) {{");
+ writeln!(out, " const char *return$ = catch$.what();");
+ writeln!(out, " throw$.len = ::std::strlen(return$);");
+ writeln!(
+ out,
+ " throw$.ptr = cxxbridge02$exception(return$, throw$.len);",
+ );
+ writeln!(out, " }}");
+ writeln!(out, " return throw$;");
+ }
writeln!(out, "}}");
}
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 88fc812..e11b2d3 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -134,7 +134,11 @@
quote!(#ident: #ty)
}
});
- let ret = expand_extern_return_type(&efn.ret, types);
+ let ret = if efn.throws {
+ quote!(-> ::cxx::private::Result)
+ } else {
+ expand_extern_return_type(&efn.ret, types)
+ };
let mut outparam = None;
if indirect_return(efn, types) {
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
@@ -153,7 +157,15 @@
let doc = &efn.doc;
let decl = expand_cxx_function_decl(namespace, efn, types);
let args = &efn.args;
- let ret = expand_return_type(&efn.ret);
+ let ret = if efn.throws {
+ let ok = match &efn.ret {
+ Some(ret) => quote!(#ret),
+ None => quote!(()),
+ };
+ quote!(-> ::std::result::Result<#ok, ::cxx::Exception>)
+ } else {
+ expand_return_type(&efn.ret)
+ };
let indirect_return = indirect_return(efn, types);
let vars = efn.args.iter().map(|arg| {
let var = &arg.ident;
@@ -192,10 +204,21 @@
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
setup.extend(quote! {
let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
- #local_name(#(#vars,)* __return.as_mut_ptr());
});
+ if efn.throws {
+ setup.extend(quote! {
+ #local_name(#(#vars,)* __return.as_mut_ptr()).exception()?;
+ });
+ quote!(::std::result::Result::Ok(__return.assume_init()))
+ } else {
+ setup.extend(quote! {
+ #local_name(#(#vars,)* __return.as_mut_ptr());
+ });
+ quote!(__return.assume_init())
+ }
+ } else if efn.throws {
quote! {
- __return.assume_init()
+ #local_name(#(#vars),*).exception()
}
} else {
quote! {
diff --git a/src/exception.rs b/src/exception.rs
new file mode 100644
index 0000000..c436196
--- /dev/null
+++ b/src/exception.rs
@@ -0,0 +1,29 @@
+use std::fmt::{self, Debug, Display};
+use std::slice;
+
+/// Exception thrown from an `extern "C"` function.
+#[derive(Debug)]
+pub struct Exception {
+ pub(crate) what: Box<str>,
+}
+
+impl Display for Exception {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(&self.what)
+ }
+}
+
+impl std::error::Error for Exception {}
+
+impl Exception {
+ pub fn what(&self) -> &str {
+ &self.what
+ }
+}
+
+#[export_name = "cxxbridge02$exception"]
+unsafe extern "C" fn exception(ptr: *const u8, len: usize) -> *const u8 {
+ let slice = slice::from_raw_parts(ptr, len);
+ let boxed = String::from_utf8_lossy(slice).into_owned().into_boxed_str();
+ Box::leak(boxed).as_ptr()
+}
diff --git a/src/lib.rs b/src/lib.rs
index b985e1d..5dd2649 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;
@@ -365,6 +366,7 @@
mod unwind;
pub use crate::cxx_string::CxxString;
+pub use crate::exception::Exception;
pub use crate::unique_ptr::UniquePtr;
pub use cxxbridge_macro::bridge;
diff --git a/src/result.rs b/src/result.rs
index 6d04a2d..047260b 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,5 +1,7 @@
+use crate::exception::Exception;
use crate::rust_str::RustStr;
use std::fmt::Display;
+use std::mem;
use std::ptr;
use std::result::Result as StdResult;
use std::slice;
@@ -41,3 +43,18 @@
let err = RustStr::from(string);
Result { err }
}
+
+impl Result {
+ pub unsafe fn exception(self) -> StdResult<(), Exception> {
+ if self.ok.is_null() {
+ Ok(())
+ } else {
+ let err = self.err;
+ let slice = slice::from_raw_parts(err.ptr.as_ptr(), err.len);
+ let s = str::from_utf8_unchecked(slice);
+ Err(Exception {
+ what: mem::transmute::<*const str, Box<str>>(s),
+ })
+ }
+ }
+}
diff --git a/src/rust_str.rs b/src/rust_str.rs
index 110889b..51e4835 100644
--- a/src/rust_str.rs
+++ b/src/rust_str.rs
@@ -7,8 +7,8 @@
#[repr(C)]
#[derive(Copy, Clone)]
pub struct RustStr {
- ptr: NonNull<u8>,
- len: usize,
+ pub(crate) ptr: NonNull<u8>,
+ pub(crate) len: usize,
}
impl RustStr {
diff --git a/syntax/check.rs b/syntax/check.rs
index b781445..ef3faff 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -1,5 +1,5 @@
use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::{error, ident, Api, ExternFn, Lang::*, Ref, Ty1, Type, Types, Var};
+use crate::syntax::{error, ident, Api, ExternFn, Ref, Ty1, Type, Types, Var};
use proc_macro2::Ident;
use syn::{Error, Result};
@@ -69,12 +69,6 @@
errors.push(return_by_value(ty, types));
}
}
- if efn.throws && efn.lang == Cxx {
- errors.push(Error::new_spanned(
- efn,
- "fallible C++ functions are not implemented yet",
- ));
- }
}
_ => {}
}
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index fd54654..fe6a364 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -30,6 +30,10 @@
fn c_take_str(s: &str);
fn c_take_rust_string(s: String);
fn c_take_unique_ptr_string(s: UniquePtr<CxxString>);
+
+ fn c_try_return_void() -> Result<()>;
+ fn c_try_return_primitive() -> Result<usize>;
+ fn c_fail_return_primitive() -> Result<usize>;
}
extern "Rust" {
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 0ec2658..4f36f9b 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -1,6 +1,7 @@
#include "tests/ffi/tests.h"
#include "tests/ffi/lib.rs"
#include <cstring>
+#include <stdexcept>
extern "C" void cxx_test_suite_set_correct() noexcept;
extern "C" tests::R *cxx_test_suite_get_box() noexcept;
@@ -91,6 +92,12 @@
}
}
+void c_try_return_void() {}
+
+size_t c_try_return_primitive() { return 2020; }
+
+size_t c_fail_return_primitive() { throw std::logic_error("logic error"); }
+
extern "C" C *cxx_test_suite_get_unique_ptr() noexcept {
return std::unique_ptr<C>(new C{2020}).release();
}
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 7cff6fa..6a5f802 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -36,4 +36,8 @@
void c_take_rust_string(rust::String s);
void c_take_unique_ptr_string(std::unique_ptr<std::string> s);
+void c_try_return_void();
+size_t c_try_return_primitive();
+size_t c_fail_return_primitive();
+
} // namespace tests
diff --git a/tests/test.rs b/tests/test.rs
index a30aefb..d86ebd2 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -38,6 +38,13 @@
.to_str()
.unwrap()
);
+
+ assert_eq!((), ffi::c_try_return_void().unwrap());
+ assert_eq!(2020, ffi::c_try_return_primitive().unwrap());
+ assert_eq!(
+ "logic error",
+ ffi::c_fail_return_primitive().unwrap_err().what(),
+ );
}
#[test]