Support calling Rust methods from C++

These methods can be declared in the bridge by naming the first
argument self and making it a reference to the containing class, e.g.,
  fn get(self: &R) -> usize;
  fn set(self: &mut R, n: usize);
This syntax requires Rust 1.43.
This commit is contained in:
Joel Galenson 2020-04-15 10:21:00 -07:00
parent 3d4f612b34
commit c1c4e7ac6b
6 changed files with 141 additions and 16 deletions

View File

@ -18,7 +18,10 @@ std::unique_ptr<ThingC> make_demo(rust::Str appname) {
return std::unique_ptr<ThingC>(new ThingC(std::string(appname)));
}
void do_thing(SharedThing state) { print_r(*state.y); }
void do_thing(SharedThing state) {
print_r(*state.y);
state.y->print();
}
} // namespace example
} // namespace org

View File

@ -19,6 +19,7 @@ mod ffi {
extern "Rust" {
type ThingR;
fn print_r(r: &ThingR);
fn print(self: &ThingR);
}
}
@ -28,6 +29,12 @@ fn print_r(r: &ThingR) {
println!("called back with r={}", r.0);
}
impl ThingR {
fn print(&self) {
println!("method called back with r={}", self.0);
}
}
fn main() {
let x = ffi::make_demo("demo of cxx::bridge");
println!("this is a {}", x.as_ref().unwrap().get_name());

View File

@ -2,7 +2,7 @@ use crate::gen::namespace::Namespace;
use crate::gen::out::OutFile;
use crate::gen::{include, Opt};
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::{Api, ExternFn, Receiver, Signature, Struct, Type, Types, Var};
use crate::syntax::{Api, ExternFn, ExternType, Receiver, Signature, Struct, Type, Types, Var};
use proc_macro2::Ident;
pub(super) fn gen(
@ -45,9 +45,25 @@ pub(super) fn gen(
}
for api in apis {
if let Api::Struct(strct) = api {
out.next_section();
write_struct(out, strct);
match api {
Api::Struct(strct) => {
out.next_section();
write_struct(out, strct);
}
Api::RustType(ety) => {
let methods = apis.iter().filter_map(|api| match api {
Api::RustFunction(efn) => match &efn.sig.receiver {
Some(rcvr) if rcvr.ident == ety.ident => Some(efn),
_ => None,
},
_ => None,
}).collect::<Vec<_>>();
if !methods.is_empty() {
out.next_section();
write_struct_with_methods(out, ety, methods);
}
}
_ => {}
}
}
@ -300,6 +316,21 @@ fn write_struct_using(out: &mut OutFile, ident: &Ident) {
writeln!(out, "using {} = {};", ident, ident);
}
fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: Vec<&ExternFn>) {
for line in ety.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
writeln!(out, "struct {} final {{", ety.ident);
for method in &methods {
write!(out, " ");
let sig = &method.sig;
let local_name = method.ident.to_string();
write_rust_function_shim_decl(out, &local_name, sig, None, false);
writeln!(out, ";");
}
writeln!(out, "}};");
}
fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
let mut has_cxx_throws = false;
for api in apis {
@ -326,7 +357,11 @@ fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
} else {
write_extern_return_type_space(out, &efn.ret, types);
}
write!(out, "{}cxxbridge02${}(", out.namespace, efn.ident);
let receiver_type = match &efn.receiver {
Some(base) => base.ident.to_string(),
None => "_".to_string(),
};
write!(out, "{}cxxbridge02${}${}(", out.namespace, receiver_type, efn.ident);
if let Some(base) = &efn.receiver {
write!(out, "{} *__receiver$", base.ident);
}
@ -471,7 +506,11 @@ fn write_function_pointer_trampoline(
}
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
let link_name = format!("{}cxxbridge02${}", out.namespace, efn.ident);
let receiver_type = match &efn.receiver {
Some(base) => base.ident.to_string(),
None => "_".to_string(),
};
let link_name = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
let indirect_call = false;
write_rust_function_decl_impl(out, &link_name, efn, types, indirect_call);
}
@ -490,6 +529,10 @@ fn write_rust_function_decl_impl(
}
write!(out, "{}(", link_name);
let mut needs_comma = false;
if let Some(base) = &sig.receiver {
write!(out, "{} &__receiver$", base.ident);
needs_comma = true;
}
for arg in &sig.args {
if needs_comma {
write!(out, ", ");
@ -519,20 +562,26 @@ fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
writeln!(out, "//{}", line);
}
let local_name = efn.ident.to_string();
let invoke = format!("{}cxxbridge02${}", out.namespace, efn.ident);
let receiver_type = match &efn.receiver {
Some(base) => base.ident.to_string(),
None => "_".to_string(),
};
let invoke = format!("{}cxxbridge02${}${}", out.namespace, receiver_type, efn.ident);
let indirect_call = false;
write_rust_function_shim_impl(out, &local_name, efn, types, &invoke, indirect_call);
}
fn write_rust_function_shim_impl(
fn write_rust_function_shim_decl(
out: &mut OutFile,
local_name: &str,
sig: &Signature,
types: &Types,
invoke: &str,
receiver: Option<&Receiver>,
indirect_call: bool,
) {
write_return_type(out, &sig.ret);
if let Some(base) = receiver {
write!(out, "{}::", base.ident);
}
write!(out, "{}(", local_name);
for (i, arg) in sig.args.iter().enumerate() {
if i > 0 {
@ -551,6 +600,21 @@ fn write_rust_function_shim_impl(
if !sig.throws {
write!(out, " noexcept");
}
}
fn write_rust_function_shim_impl(
out: &mut OutFile,
local_name: &str,
sig: &Signature,
types: &Types,
invoke: &str,
indirect_call: bool,
) {
if out.header && sig.receiver.is_some() {
// We've already defined this inside the struct.
return;
}
write_rust_function_shim_decl(out, local_name, sig, sig.receiver.as_ref(), indirect_call);
if out.header {
writeln!(out, ";");
} else {
@ -589,8 +653,11 @@ fn write_rust_function_shim_impl(
write!(out, "::rust::Str::Repr error$ = ");
}
write!(out, "{}(", invoke);
if let Some(_) = &sig.receiver {
write!(out, "*this");
}
for (i, arg) in sig.args.iter().enumerate() {
if i > 0 {
if i > 0 || sig.receiver.is_some() {
write!(out, ", ");
}
match &arg.ty {

View File

@ -154,7 +154,11 @@ fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
outparam = Some(quote!(__return: *mut #ret));
}
let link_name = format!("{}cxxbridge02${}", namespace, ident);
let receiver_type = match &efn.receiver {
Some(base) => base.ident.to_string(),
None => "_".to_string(),
};
let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident);
let local_name = format_ident!("__{}", ident);
quote! {
#[link_name = #link_name]
@ -366,7 +370,11 @@ fn expand_rust_type(ety: &ExternType) -> TokenStream {
fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
let ident = &efn.ident;
let link_name = format!("{}cxxbridge02${}", namespace, ident);
let receiver_type = match &efn.receiver {
Some(base) => base.ident.to_string(),
None => "_".to_string(),
};
let link_name = format!("{}cxxbridge02${}${}", namespace, receiver_type, ident);
let local_name = format_ident!("__{}", ident);
let catch_unwind_label = format!("::{}", ident);
let invoke = Some(ident);
@ -388,6 +396,13 @@ fn expand_rust_function_shim_impl(
catch_unwind_label: String,
invoke: Option<&Ident>,
) -> TokenStream {
let receiver = sig.receiver.iter().map(|base| {
let ident = &base.ident;
match base.mutability {
None => quote!(__receiver: &#ident),
Some(_) => quote!(__receiver: &mut #ident),
}
});
let args = sig.args.iter().map(|arg| {
let ident = &arg.ident;
let ty = expand_extern_type(&arg.ty);
@ -397,6 +412,7 @@ fn expand_rust_function_shim_impl(
quote!(#ident: #ty)
}
});
let all_args = receiver.chain(args);
let vars = sig.args.iter().map(|arg| {
let ident = &arg.ident;
@ -418,7 +434,10 @@ fn expand_rust_function_shim_impl(
});
let mut call = match invoke {
Some(ident) => quote!(super::#ident),
Some(ident) => match sig.receiver {
None => quote!(super::#ident),
Some(_) => quote!(__receiver.#ident),
},
None => quote!(__extern),
};
call.extend(quote! { (#(#vars),*) });
@ -476,7 +495,7 @@ fn expand_rust_function_shim_impl(
quote! {
#[doc(hidden)]
#[export_name = #link_name]
unsafe extern "C" fn #local_name(#(#args,)* #outparam #pointer) #ret {
unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret {
let __fn = concat!(module_path!(), #catch_unwind_label);
#expr
}

View File

@ -56,6 +56,7 @@ pub mod ffi {
extern "Rust" {
type R;
type R2;
fn r_return_primitive() -> usize;
fn r_return_shared() -> Shared;
@ -80,11 +81,28 @@ pub mod ffi {
fn r_try_return_void() -> Result<()>;
fn r_try_return_primitive() -> Result<usize>;
fn r_fail_return_primitive() -> Result<usize>;
fn r_return_r2(n: usize) -> Box<R2>;
fn get(self: &R2) -> usize;
fn set(self: &mut R2, n: usize) -> usize;
}
}
pub type R = usize;
pub struct R2(usize);
impl R2 {
fn get(&self) -> usize {
self.0
}
fn set(&mut self, n: usize) -> usize {
self.0 = n;
n
}
}
#[derive(Debug)]
struct Error;
@ -187,3 +205,7 @@ fn r_try_return_primitive() -> Result<usize, Error> {
fn r_fail_return_primitive() -> Result<usize, Error> {
Err(Error)
}
fn r_return_r2(n: usize) -> Box<R2> {
Box::new(R2(n))
}

View File

@ -181,6 +181,13 @@ extern "C" const char *cxx_run_test() noexcept {
ASSERT(std::strcmp(e.what(), "rust error") == 0);
}
auto r2 = r_return_r2(2020);
ASSERT(r2->get() == 2020);
ASSERT(r2->set(2021) == 2021);
ASSERT(r2->get() == 2021);
ASSERT(r2->set(2020) == 2020);
ASSERT(r2->get() == 2020);
cxx_test_suite_set_correct();
return nullptr;
}