Skip to content

Commit 5ffc29a

Browse files
committed
Minimal support for Rust type aliases
The intention is to provide support to re-import Rust types already exported elsewhere into other bridges, creating equivalence of `extern "Rust"` types on the C++ side. Currently, the support is limited to re-exporting the type under the same name and the C++ namespace must be explicitly specified. Then, C++ functions can use the same type.
1 parent 03b0c8d commit 5ffc29a

18 files changed

+182
-20
lines changed

book/src/extern-rust.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,48 @@ mod ffi {
163163

164164
Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are
165165
type parameters or where-clauses.
166+
167+
## Type equivalence across bridges
168+
169+
Similar to type aliases for C++ types, it is possible to create type aliases for
170+
previously-exported types via the `extern "Rust"` block in another bridge.
171+
However, current support is very limited:
172+
173+
- The type name must be the same as that of the target type.
174+
- If the target is in a different C++ namespace, then the namespace must be
175+
explicitly specified on the alias, otherwise C++ won't consider the types as
176+
equivalent.
177+
178+
Basically, this is enough to import the type from another bridge and nothing more.
179+
180+
In the first module `crate::mod1`, you export the type:
181+
182+
```rust,noplayground
183+
pub struct MyType {
184+
...
185+
}
186+
187+
#[cxx::bridge(namespace = "mod1")]
188+
mod ffi {
189+
extern "Rust" {
190+
type MyType;
191+
}
192+
}
193+
```
194+
195+
And in another crate/module `mod2`, you can now import the type and use it as
196+
a parameter or a return value in C++ and Rust functions:
197+
198+
```rust,noplayground
199+
#[cxx::bridge(namespace = "mod2")]
200+
mod ffi {
201+
extern "Rust" {
202+
#[namespace = "mod1"]
203+
type MyType = crate::mod1::MyType;
204+
}
205+
206+
extern "C++" {
207+
fn c_func(param: &MyType);
208+
}
209+
}
210+
```

gen/src/namespace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ impl Api {
66
match self {
77
Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.name.namespace,
88
Api::CxxType(ety) | Api::RustType(ety) => &ety.name.namespace,
9+
Api::TypeAlias(ety) => &ety.name.namespace,
910
Api::Enum(enm) => &enm.name.namespace,
1011
Api::Struct(strct) => &strct.name.namespace,
11-
Api::Impl(_) | Api::Include(_) | Api::TypeAlias(_) => Default::default(),
12+
Api::Impl(_) | Api::Include(_) => Default::default(),
1213
}
1314
}
1415
}

gen/src/write.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::syntax::set::UnorderedSet;
99
use crate::syntax::symbol::{self, Symbol};
1010
use crate::syntax::trivial::{self, TrivialReason};
1111
use crate::syntax::{
12-
derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait,
13-
Type, TypeAlias, Types, Var,
12+
derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Lang, Pair, Signature, Struct,
13+
Trait, Type, TypeAlias, Types, Var,
1414
};
1515
use proc_macro2::Ident;
1616

@@ -35,6 +35,7 @@ pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u
3535
fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) {
3636
let needs_forward_declaration = |api: &&Api| match api {
3737
Api::Struct(_) | Api::CxxType(_) | Api::RustType(_) => true,
38+
Api::TypeAlias(ety) => ety.lang == Lang::Rust,
3839
Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust),
3940
_ => false,
4041
};
@@ -54,6 +55,7 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) {
5455
Api::Enum(enm) => write_enum_decl(out, enm),
5556
Api::CxxType(ety) => write_struct_using(out, &ety.name),
5657
Api::RustType(ety) => write_struct_decl(out, &ety.name),
58+
Api::TypeAlias(ety) => write_struct_decl(out, &ety.name),
5759
_ => unreachable!(),
5860
}
5961
}
@@ -128,8 +130,17 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
128130
out.next_section();
129131
for api in apis {
130132
if let Api::TypeAlias(ety) = api {
131-
if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
132-
check_trivial_extern_type(out, ety, reasons)
133+
match ety.lang {
134+
Lang::Cxx => {
135+
if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) {
136+
check_trivial_extern_type(out, ety, reasons)
137+
}
138+
}
139+
Lang::Rust => {
140+
// nothing to write here, the alias is only used to generate
141+
// forward declaration in C++ (so C++ shims for Rust functions
142+
// using the type compile correctly).
143+
}
133144
}
134145
}
135146
}

macro/src/expand.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::{derive, generics};
1515
use proc_macro2::{Ident, Span, TokenStream};
1616
use quote::{format_ident, quote, quote_spanned, ToTokens};
1717
use std::mem;
18+
use syn::spanned::Spanned;
1819
use syn::{parse_quote, punctuated, Generics, Lifetime, Result, Token};
1920

2021
pub fn bridge(mut ffi: Module) -> Result<TokenStream> {
@@ -84,10 +85,16 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
8485
hidden.extend(expand_rust_type_layout(ety, types));
8586
}
8687
Api::RustFunction(efn) => hidden.extend(expand_rust_function_shim(efn, types)),
87-
Api::TypeAlias(alias) => {
88-
expanded.extend(expand_type_alias(alias));
89-
hidden.extend(expand_type_alias_verify(alias, types));
90-
}
88+
Api::TypeAlias(alias) => match alias.lang {
89+
syntax::Lang::Cxx => {
90+
expanded.extend(expand_type_alias(alias));
91+
hidden.extend(expand_type_alias_verify(alias, types));
92+
}
93+
syntax::Lang::Rust => {
94+
expanded.extend(expand_type_alias_rust(alias));
95+
hidden.extend(expand_type_alias_verify_rust(alias));
96+
}
97+
},
9198
}
9299
}
93100

@@ -1217,6 +1224,24 @@ fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
12171224
}
12181225
}
12191226

1227+
fn expand_type_alias_rust(alias: &TypeAlias) -> TokenStream {
1228+
let doc = &alias.doc;
1229+
let attrs = &alias.attrs;
1230+
let visibility = alias.visibility;
1231+
let _type_token = alias.type_token;
1232+
let ident = &alias.name.rust;
1233+
let _generics = &alias.generics;
1234+
let _eq_token = alias.eq_token;
1235+
let ty = &alias.ty;
1236+
let semi_token = alias.semi_token;
1237+
1238+
quote! {
1239+
#doc
1240+
#attrs
1241+
#visibility use #ty as #ident #semi_token
1242+
}
1243+
}
1244+
12201245
fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream {
12211246
let ident = &alias.name.rust;
12221247
let type_id = type_id(&alias.name);
@@ -1239,6 +1264,15 @@ fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream {
12391264
verify
12401265
}
12411266

1267+
fn expand_type_alias_verify_rust(alias: &TypeAlias) -> TokenStream {
1268+
let mut ident = alias.name.rust.clone();
1269+
let span = alias.ty.span();
1270+
ident.set_span(span);
1271+
quote_spanned! {span=>
1272+
const _: fn() = ::cxx::private::verify_rust_type::< #ident >;
1273+
}
1274+
}
1275+
12421276
fn type_id(name: &Pair) -> TokenStream {
12431277
let namespace_segments = name.namespace.iter();
12441278
let mut segments = Vec::with_capacity(namespace_segments.len() + 1);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ pub mod private {
506506
pub use crate::rust_str::RustStr;
507507
#[cfg(feature = "alloc")]
508508
pub use crate::rust_string::RustString;
509-
pub use crate::rust_type::{ImplBox, ImplVec, RustType};
509+
pub use crate::rust_type::{ImplBox, ImplVec, RustType, verify_rust_type};
510510
#[cfg(feature = "alloc")]
511511
pub use crate::rust_vec::RustVec;
512512
pub use crate::shared_ptr::SharedPtrTarget;

src/rust_type.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
pub unsafe trait RustType {}
44
pub unsafe trait ImplBox {}
55
pub unsafe trait ImplVec {}
6+
7+
#[doc(hidden)]
8+
pub fn verify_rust_type<T: RustType>() {}

syntax/check.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use crate::syntax::report::Errors;
33
use crate::syntax::visit::{self, Visit};
44
use crate::syntax::{
55
error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Lifetimes,
6-
NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
6+
NamedType, Ptr, Receiver, Ref, RustType, Signature, SliceRef, Struct, Trait, Ty1, Type,
7+
TypeAlias, Types,
78
};
89
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
910
use quote::{quote, ToTokens};
@@ -500,6 +501,25 @@ fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) {
500501
let msg = format!("derive({}) on extern type alias is not supported", derive);
501502
cx.error(derive, msg);
502503
}
504+
505+
if alias.lang == Lang::Rust {
506+
let ty = &alias.ty;
507+
if let RustType::Path(path) = &ty {
508+
// OK, we support path
509+
if let Some(last) = path.path.segments.last() {
510+
if last.ident != alias.name.rust {
511+
cx.error(
512+
&alias.name.rust,
513+
"`extern \"Rust\"` alias must have the same name as the target type",
514+
);
515+
}
516+
} else {
517+
cx.error(ty, "unsupported `extern \"Rust\"` alias target type");
518+
}
519+
} else {
520+
cx.error(ty, "unsupported `extern \"Rust\"` alias target");
521+
}
522+
}
503523
}
504524

505525
fn check_api_impl(cx: &mut Check, imp: &Impl) {

syntax/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ pub struct TypeAlias {
154154
pub visibility: Token![pub],
155155
pub type_token: Token![type],
156156
pub name: Pair,
157+
pub lang: Lang,
157158
pub generics: Lifetimes,
158159
pub eq_token: Token![=],
159160
pub ty: RustType,

syntax/parse.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -874,12 +874,6 @@ fn parse_type_alias(
874874
},
875875
));
876876

877-
if lang == Lang::Rust {
878-
let span = quote!(#type_token #semi_token);
879-
let msg = "type alias in extern \"Rust\" block is not supported";
880-
return Err(Error::new_spanned(span, msg));
881-
}
882-
883877
let visibility = visibility_pub(&visibility, type_token.span);
884878
let name = pair(namespace, &ident, cxx_name, rust_name);
885879

@@ -891,6 +885,7 @@ fn parse_type_alias(
891885
visibility,
892886
type_token,
893887
name,
888+
lang,
894889
generics,
895890
eq_token,
896891
ty,

tests/ffi/module.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ pub mod ffi {
1717

1818
#[cxx::bridge(namespace = "tests")]
1919
pub mod ffi2 {
20+
extern "Rust" {
21+
type R = crate::R;
22+
}
23+
2024
unsafe extern "C++" {
2125
include!("tests/ffi/tests.h");
2226

@@ -69,6 +73,8 @@ pub mod ffi2 {
6973

7074
#[namespace = "I"]
7175
fn ns_c_return_unique_ptr_ns() -> UniquePtr<I>;
76+
77+
fn c_return_box_from_aliased_rust_type() -> Box<R>;
7278
}
7379

7480
impl UniquePtr<D> {}

0 commit comments

Comments
 (0)