Skip to content

Commit 9f69230

Browse files
authored
Merge pull request #325 from adetaylor/with-trivial-2
Allow type aliases to be marked as Trivial.
2 parents 4954ca1 + feb0dc1 commit 9f69230

File tree

7 files changed

+150
-13
lines changed

7 files changed

+150
-13
lines changed

gen/src/write.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ pub(super) fn gen(
8080
write_struct_with_methods(out, ety, methods);
8181
}
8282
}
83+
Api::TypeAlias(ety) => {
84+
if types.required_trivial_aliases.contains(&ety.ident) {
85+
check_trivial_extern_type(out, &ety.ident)
86+
}
87+
}
8388
_ => {}
8489
}
8590
}
@@ -124,13 +129,18 @@ pub(super) fn gen(
124129
fn write_includes(out: &mut OutFile, types: &Types) {
125130
for ty in types {
126131
match ty {
127-
Type::Ident(ident) => match Atom::from(ident) {
128-
Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(I8) | Some(I16) | Some(I32)
129-
| Some(I64) => out.include.cstdint = true,
130-
Some(Usize) => out.include.cstddef = true,
131-
Some(CxxString) => out.include.string = true,
132-
Some(Bool) | Some(Isize) | Some(F32) | Some(F64) | Some(RustString) | None => {}
133-
},
132+
Type::Ident(ident) => {
133+
match Atom::from(ident) {
134+
Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(I8) | Some(I16)
135+
| Some(I32) | Some(I64) => out.include.cstdint = true,
136+
Some(Usize) => out.include.cstddef = true,
137+
Some(CxxString) => out.include.string = true,
138+
Some(Bool) | Some(Isize) | Some(F32) | Some(F64) | Some(RustString) | None => {}
139+
};
140+
if types.required_trivial_aliases.contains(&ident) {
141+
out.include.type_traits = true;
142+
};
143+
}
134144
Type::RustBox(_) => out.include.type_traits = true,
135145
Type::UniquePtr(_) => out.include.memory = true,
136146
Type::CxxVector(_) => out.include.vector = true,
@@ -401,6 +411,11 @@ fn check_enum(out: &mut OutFile, enm: &Enum) {
401411
}
402412
}
403413

414+
fn check_trivial_extern_type(out: &mut OutFile, id: &Ident) {
415+
writeln!(out, "static_assert(std::is_trivially_move_constructible<{}>::value,\"type {} marked as Trivial in Rust is not trivially move constructible in C++\");", id, id);
416+
writeln!(out, "static_assert(std::is_trivially_destructible<{}>::value,\"type {} marked as Trivial in Rust is not trivially destructible in C++\");", id, id);
417+
}
418+
404419
fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
405420
let mut has_cxx_throws = false;
406421
for api in apis {

macro/src/expand.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
5757
Api::TypeAlias(alias) => {
5858
expanded.extend(expand_type_alias(alias));
5959
hidden.extend(expand_type_alias_verify(namespace, alias));
60+
let ident = &alias.ident;
61+
if types.required_trivial_aliases.contains(ident) {
62+
hidden.extend(expand_type_alias_kind_trivial_verify(alias));
63+
}
6064
}
6165
}
6266
}
@@ -179,6 +183,7 @@ fn expand_cxx_type(namespace: &Namespace, ety: &ExternType) -> TokenStream {
179183

180184
unsafe impl ::cxx::ExternType for #ident {
181185
type Id = #type_id;
186+
type Kind = ::cxx::Opaque;
182187
}
183188
}
184189
}
@@ -679,6 +684,18 @@ fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenSt
679684
}
680685
}
681686

687+
fn expand_type_alias_kind_trivial_verify(type_alias: &TypeAlias) -> TokenStream {
688+
let ident = &type_alias.ident;
689+
let begin_span = type_alias.type_token.span;
690+
let end_span = type_alias.semi_token.span;
691+
let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_kind::<);
692+
let end = quote_spanned!(end_span=> >);
693+
694+
quote! {
695+
const _: fn() = #begin #ident, ::cxx::Trivial #end;
696+
}
697+
}
698+
682699
fn type_id(namespace: &Namespace, ident: &Ident) -> TokenStream {
683700
let mut path = String::new();
684701
for name in namespace {

src/extern_type.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
/// ## Integrating with bindgen-generated types
5555
///
5656
/// Handwritten `ExternType` impls make it possible to plug in a data structure
57-
/// emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
57+
/// emitted by bindgen as the definition of a C++ type emitted by CXX.
5858
///
5959
/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++
6060
/// namespace and type name given in the type id refers to a C++ type that is
@@ -69,10 +69,11 @@
6969
/// # pub struct StringPiece([usize; 2]);
7070
/// # }
7171
///
72-
/// use cxx::{type_id, ExternType};
72+
/// use cxx::{type_id, ExternType, Opaque};
7373
///
7474
/// unsafe impl ExternType for folly_sys::StringPiece {
7575
/// type Id = type_id!("folly::StringPiece");
76+
/// type Kind = Opaque;
7677
/// }
7778
///
7879
/// #[cxx::bridge(namespace = folly)]
@@ -92,6 +93,29 @@
9293
/// #
9394
/// # fn main() {}
9495
/// ```
96+
///
97+
/// ## Opaque and Trivial types
98+
///
99+
/// Some C++ types are safe to hold and pass around in Rust, by value.
100+
/// Those C++ types must have a trivial move constructor, and must
101+
/// have no destructor.
102+
///
103+
/// If you believe your C++ type is indeed trivial, you can specify
104+
/// ```
105+
/// # struct TypeName;
106+
/// # unsafe impl cxx::ExternType for TypeName {
107+
/// type Id = cxx::type_id!("name::space::of::TypeName");
108+
/// type Kind = cxx::Trivial;
109+
/// # }
110+
/// ```
111+
/// which will enable you to pass it into C++ functions by value,
112+
/// return it by value from such functions, and include it in
113+
/// `struct`s that you have declared to `cxx::bridge`. Your promises
114+
/// about the triviality of the C++ type will be checked using
115+
/// `static_assert`s in the generated C++.
116+
///
117+
/// Opaque types can't be passed by value, but can still be held
118+
/// in `UniquePtr`.
95119
pub unsafe trait ExternType {
96120
/// A type-level representation of the type's C++ namespace and type name.
97121
///
@@ -101,10 +125,32 @@ pub unsafe trait ExternType {
101125
/// # struct TypeName;
102126
/// # unsafe impl cxx::ExternType for TypeName {
103127
/// type Id = cxx::type_id!("name::space::of::TypeName");
128+
/// type Kind = cxx::Opaque;
104129
/// # }
105130
/// ```
106131
type Id;
132+
133+
/// Either `cxx::Opaque` or `cxx::Trivial`. If in doubt, use
134+
/// `cxx::Opaque`.
135+
type Kind;
136+
}
137+
138+
pub(crate) mod kind {
139+
140+
/// An opaque type which can't be passed or held by value within Rust.
141+
/// For example, a C++ type with a destructor, or a non-trivial move
142+
/// constructor. Rust's strict move semantics mean that we can't own
143+
/// these by value in Rust, but they can still be owned by a
144+
/// `UniquePtr`...
145+
pub struct Opaque;
146+
147+
/// A type with trivial move constructors and no destructor, which
148+
/// can therefore be owned and moved around in Rust code directly.
149+
pub struct Trivial;
107150
}
108151

109152
#[doc(hidden)]
110153
pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
154+
155+
#[doc(hidden)]
156+
pub fn verify_extern_kind<T: ExternType<Kind = Kind>, Kind>() {}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ mod unwind;
395395
pub use crate::cxx_string::CxxString;
396396
pub use crate::cxx_vector::CxxVector;
397397
pub use crate::exception::Exception;
398+
pub use crate::extern_type::kind::Opaque;
399+
pub use crate::extern_type::kind::Trivial;
398400
pub use crate::extern_type::ExternType;
399401
pub use crate::unique_ptr::UniquePtr;
400402
pub use cxxbridge_macro::bridge;
@@ -422,6 +424,7 @@ pub type Vector<T> = CxxVector<T>;
422424
#[doc(hidden)]
423425
pub mod private {
424426
pub use crate::cxx_vector::VectorElement;
427+
pub use crate::extern_type::verify_extern_kind;
425428
pub use crate::extern_type::verify_extern_type;
426429
pub use crate::function::FatFunction;
427430
pub use crate::opaque::Opaque;

syntax/check.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
338338
|| cx.types.cxx.contains(ident)
339339
&& !cx.types.structs.contains_key(ident)
340340
&& !cx.types.enums.contains_key(ident)
341+
&& !cx.types.required_trivial_aliases.contains(ident)
341342
|| cx.types.rust.contains(ident)
342343
}
343344

@@ -376,7 +377,11 @@ fn describe(cx: &mut Check, ty: &Type) -> String {
376377
} else if cx.types.enums.contains_key(ident) {
377378
"enum".to_owned()
378379
} else if cx.types.cxx.contains(ident) {
379-
"C++ type".to_owned()
380+
if cx.types.required_trivial_aliases.contains(ident) {
381+
"trivial C++ type".to_owned()
382+
} else {
383+
"non-trivial C++ type".to_owned()
384+
}
380385
} else if cx.types.rust.contains(ident) {
381386
"opaque Rust type".to_owned()
382387
} else if Atom::from(ident) == Some(CxxString) {

syntax/types.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Types<'a> {
1414
pub rust: Set<&'a Ident>,
1515
pub aliases: Map<&'a Ident, &'a TypeAlias>,
1616
pub untrusted: Map<&'a Ident, &'a ExternType>,
17+
pub required_trivial_aliases: Set<&'a Ident>,
1718
}
1819

1920
impl<'a> Types<'a> {
@@ -135,6 +136,55 @@ impl<'a> Types<'a> {
135136
}
136137
}
137138

139+
// All these APIs may contain types passed by value. We need to ensure
140+
// we check that this is permissible. We do this _after_ scanning all
141+
// the APIs above, in case some function or struct references a type
142+
// which is declared subsequently.
143+
let mut required_trivial_aliases = Set::new();
144+
145+
fn insist_alias_types_are_trivial<'c>(
146+
required_trivial_aliases: &mut Set<&'c Ident>,
147+
aliases: &Map<&'c Ident, &'c TypeAlias>,
148+
ty: &'c Type,
149+
) {
150+
if let Type::Ident(ident) = ty {
151+
if aliases.contains_key(ident) {
152+
required_trivial_aliases.insert(ident);
153+
}
154+
}
155+
}
156+
157+
for api in apis {
158+
match api {
159+
Api::Struct(strct) => {
160+
for field in &strct.fields {
161+
insist_alias_types_are_trivial(
162+
&mut required_trivial_aliases,
163+
&aliases,
164+
&field.ty,
165+
);
166+
}
167+
}
168+
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
169+
for arg in &efn.args {
170+
insist_alias_types_are_trivial(
171+
&mut required_trivial_aliases,
172+
&aliases,
173+
&arg.ty,
174+
);
175+
}
176+
if let Some(ret) = &efn.ret {
177+
insist_alias_types_are_trivial(
178+
&mut required_trivial_aliases,
179+
&aliases,
180+
&ret,
181+
);
182+
}
183+
}
184+
_ => {}
185+
}
186+
}
187+
138188
Types {
139189
all,
140190
structs,
@@ -143,6 +193,7 @@ impl<'a> Types<'a> {
143193
rust,
144194
aliases,
145195
untrusted,
196+
required_trivial_aliases,
146197
}
147198
}
148199

tests/ui/by_value_not_supported.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: using C++ type by value is not supported
1+
error: using non-trivial C++ type by value is not supported
22
--> $DIR/by_value_not_supported.rs:4:9
33
|
44
4 | c: C,
@@ -16,13 +16,13 @@ error: using C++ string by value is not supported
1616
6 | s: CxxString,
1717
| ^^^^^^^^^^^^
1818

19-
error: passing C++ type by value is not supported
19+
error: passing non-trivial C++ type by value is not supported
2020
--> $DIR/by_value_not_supported.rs:16:14
2121
|
2222
16 | fn f(c: C) -> C;
2323
| ^^^^
2424

25-
error: returning C++ type by value is not supported
25+
error: returning non-trivial C++ type by value is not supported
2626
--> $DIR/by_value_not_supported.rs:16:23
2727
|
2828
16 | fn f(c: C) -> C;

0 commit comments

Comments
 (0)