Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement shared type aliases from another bridge module #308

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1007,12 +1007,14 @@ fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
out.next_section();
write_rust_box_extern(out, inner);
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
out.next_section();
write_rust_box_extern(out, inner);
}
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
if Atom::from(inner).is_none() {
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
out.next_section();
write_rust_vec_extern(out, inner);
}
Expand Down Expand Up @@ -1040,11 +1042,13 @@ fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
write_rust_box_impl(out, inner);
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
write_rust_box_impl(out, inner);
}
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
if Atom::from(inner).is_none() {
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
write_rust_vec_impl(out, inner);
}
}
Expand Down
36 changes: 28 additions & 8 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, TypeAlias, Types,
self, check, mangle, AliasKind, Api, Enum, ExternFn, ExternType, Signature, Struct, Type,
TypeAlias, Types,
};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
Expand Down Expand Up @@ -40,8 +41,8 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
for api in apis {
match api {
Api::Include(_) | Api::RustType(_) => {}
Api::Struct(strct) => expanded.extend(expand_struct(strct)),
Api::Enum(enm) => expanded.extend(expand_enum(enm)),
Api::Struct(strct) => expanded.extend(expand_struct(namespace, strct)),
Api::Enum(enm) => expanded.extend(expand_enum(namespace, enm)),
Api::CxxType(ety) => {
let ident = &ety.ident;
if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) {
Expand All @@ -64,13 +65,13 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
for ty in types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(ident) = &ty.inner {
if Atom::from(ident).is_none() {
if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
hidden.extend(expand_rust_box(namespace, ident));
}
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(ident) = &ty.inner {
if Atom::from(ident).is_none() {
if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
hidden.extend(expand_rust_vec(namespace, ident));
}
}
Expand Down Expand Up @@ -119,7 +120,7 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
}
}

fn expand_struct(strct: &Struct) -> TokenStream {
fn expand_struct(namespace: &Namespace, strct: &Struct) -> TokenStream {
let ident = &strct.ident;
let doc = &strct.doc;
let derives = &strct.derives;
Expand All @@ -129,17 +130,23 @@ fn expand_struct(strct: &Struct) -> TokenStream {
let vis = Token![pub](field.ident.span());
quote!(#vis #field)
});
let type_id = type_id(namespace, ident);
quote! {
#doc
#[derive(#(#derives),*)]
#[repr(C)]
pub struct #ident {
#(#fields,)*
}

unsafe impl ::cxx::ExternType for #ident {
type Kind = ::cxx::ExternTypeKindShared;
type Id = #type_id;
}
}
}

fn expand_enum(enm: &Enum) -> TokenStream {
fn expand_enum(namespace: &Namespace, enm: &Enum) -> TokenStream {
let ident = &enm.ident;
let doc = &enm.doc;
let repr = enm.repr;
Expand All @@ -150,6 +157,7 @@ fn expand_enum(enm: &Enum) -> TokenStream {
pub const #variant_ident: Self = #ident { repr: #discriminant };
})
});
let type_id = type_id(namespace, ident);
quote! {
#doc
#[derive(Copy, Clone, PartialEq, Eq)]
Expand All @@ -162,6 +170,11 @@ fn expand_enum(enm: &Enum) -> TokenStream {
impl #ident {
#(#variants)*
}

unsafe impl ::cxx::ExternType for #ident {
type Kind = ::cxx::ExternTypeKindShared;
type Id = #type_id;
}
}
}

Expand All @@ -178,6 +191,7 @@ fn expand_cxx_type(namespace: &Namespace, ety: &ExternType) -> TokenStream {
}

unsafe impl ::cxx::ExternType for #ident {
type Kind = ::cxx::ExternTypeKindOpaqueCpp;
type Id = #type_id;
}
}
Expand Down Expand Up @@ -657,9 +671,11 @@ fn expand_rust_function_shim_impl(
}

fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
let doc = &alias.doc;
let ident = &alias.ident;
let ty = &alias.ty;
quote! {
#doc
pub type #ident = #ty;
}
}
Expand All @@ -669,11 +685,15 @@ fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenSt
let type_id = type_id(namespace, ident);
let begin_span = alias.type_token.span;
let end_span = alias.semi_token.span;
let kind = match alias.kind {
AliasKind::Shared => quote!(ExternTypeKindShared),
AliasKind::OpaqueCpp => quote!(ExternTypeKindOpaqueCpp),
};
let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<);
let end = quote_spanned!(end_span=> >);

quote! {
const _: fn() = #begin #ident, #type_id #end;
const _: fn() = #begin #ident, ::cxx:: #kind, #type_id #end;
}
}

Expand Down
161 changes: 132 additions & 29 deletions src/extern_type.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
/// A type for which the layout is determined by its C++ definition.
/// A type for which the layout is determined by an external definition.
///
/// This trait serves the following two related purposes.
/// `ExternType` makes it possible for CXX to safely share a consistent Rust type across multiple
/// #\[cxx::bridge\] invocations, both for shared types defined in another bridge and external C++
/// definitions. This serves multiple related purposes.
///
/// <br>
///
/// ## Safely unifying occurrences of the same extern type
///
/// `ExternType` makes it possible for CXX to safely share a consistent Rust
/// type across multiple #\[cxx::bridge\] invocations that refer to a common
/// extern C++ type.
///
/// In the following snippet, two #\[cxx::bridge\] invocations in different
/// files (possibly different crates) both contain function signatures involving
/// the same C++ type `example::Demo`. If both were written just containing
/// `type Demo;`, then both macro expansions would produce their own separate
/// Rust type called `Demo` and thus the compiler wouldn't allow us to take the
/// `Demo` returned by `file1::ffi::create_demo` and pass it as the `Demo`
/// argument accepted by `file2::ffi::take_ref_demo`. Instead, one of the two
/// `Demo`s has been defined as an extern type alias of the other, making them
/// the same type in Rust. The CXX code generator will use an automatically
/// generated `ExternType` impl emitted in file1 to statically verify that in
/// file2 `crate::file1::ffi::Demo` really does refer to the C++ type
/// ## Safely unifying occurrences of the same extern C++ type
///
/// In the following snippet, two #\[cxx::bridge\] invocations in different files (possibly
/// different crates) both contain function signatures involving the same C++ type `example::Demo`.
///
/// If both were written just containing `type Demo;`, then both macro expansions would produce
/// their own separate Rust type called `Demo` and thus the compiler wouldn't allow us to take the
/// `Demo` returned by `file1::ffi::create_demo` and pass it as the `Demo` argument accepted by
/// `file2::ffi::take_ref_demo`. Instead, one of the two `Demo`s has been defined as an extern type
/// alias of the other, making them the same type in Rust.
///
/// The CXX code generator will use an automatically generated `ExternType` impl emitted in file1 to
/// statically verify that in file2 `crate::file1::ffi::Demo` really does refer to the C++ type
/// `example::Demo` as expected in file2.
///
/// ```no_run
Expand Down Expand Up @@ -51,14 +49,104 @@
///
/// <br><br>
///
/// ## Reusing Rust/C++ shared types across multiple bridges
///
/// `ExternType` enables reusing a shared Rust/C++ type declared in another bridge module, allowing
/// for the creation of libraries to wrap types used in multiple different bridges.
///
/// Imagine we have an existing move-only C++ type, file::UniqueFd, that wraps sole ownership of a
/// file descriptor, analogous to Rust's std::fd::File. The example below defines a shared type
/// `File` that allows safely transferring ownership of the file across the interface without Box or
/// UniquePtr and without resource leaks. This type can then be reused in other bridges.
///
/// ```no_run
/// // file/src/lib.rs
/// # #[cfg(unix)]
/// # mod file {
/// # use std::os::unix::io::{IntoRawFd, FromRawFd};
/// #[cxx::bridge(namespace = file::ffi)]
/// pub mod ffi {
/// /// A file backed by a file descriptor, which it is the sole owner of.
/// struct File {
/// fd: i32,
/// }
/// }
///
/// impl From<ffi::File> for std::fs::File {
/// fn from(value: ffi::File) -> Self {
/// // Safe because ffi::File owns its file descriptor.
/// unsafe { Self::from_raw_fd(value.fd) }
/// }
/// }
///
/// impl From<std::fs::File> for ffi::File {
/// fn from(value: std::fs::File) -> Self {
/// Self { fd: value.into_raw_fd() }
/// }
/// }
///
/// impl Drop for ffi::File {
/// fn drop(&mut self) {
/// // Safe because ffi::File owns its file descriptor.
/// unsafe { std::fs::File::from_raw_fd(self.fd); }
/// }
/// }
/// # }
///
/// // file/src/lib.h
/// # /*
/// namespace file {
///
/// ffi::File TransferToFFI(File file) {
/// // Imagine file::UniqueFd::release() is analogous to from_raw_fd
/// return ffi::File{ .fd = file.release() };
/// }
///
/// }
/// # */
///
/// // TODO(https://github.com/dtolnay/cxx/pull/298): Currently this bridge must use the same
/// // namespace as any bridge it creates aliases from.
///
/// // usage.rs
/// # #[cfg(unix)]
/// # mod usage {
/// #[cxx::bridge(namespace = file::ffi)]
/// pub mod ffi {
/// type File = crate::file::ffi::File;
///
/// extern "C" {
/// type Demo;
///
/// fn create_demo(file: File) -> UniquePtr<Demo>;
/// }
/// }
/// # }
///
/// // usage.cc
/// # /*
/// file::ffi::File ConvertFile(file::UniqueFd file) {
/// }
///
/// void CreateDemo(file::UniqueFd file) {
/// auto demo = ffi::create_demo(file::TransferToFFI(std::move(file)));
/// // use demo
/// }
/// # */
///
/// # fn main() {}
/// ```
///
/// <br><br>
///
/// ## Integrating with bindgen-generated types
///
/// Handwritten `ExternType` impls make it possible to plug in a data structure
/// emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
/// Handwritten `ExternType` impls make it possible to plug in a data structure emitted by bindgen
/// as the definition of an opaque C++ type emitted by CXX.
///
/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++
/// namespace and type name given in the type id refers to a C++ type that is
/// equivalent to Rust type that is the `Self` type of the impl.
/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++ namespace and type
/// name given in the type id refers to a C++ type that is equivalent to Rust type that is the
/// `Self` type of the impl.
///
/// ```no_run
/// # const _: &str = stringify! {
Expand All @@ -69,10 +157,9 @@
/// # pub struct StringPiece([usize; 2]);
/// # }
///
/// use cxx::{type_id, ExternType};
///
/// unsafe impl ExternType for folly_sys::StringPiece {
/// type Id = type_id!("folly::StringPiece");
/// unsafe impl cxx::ExternType for folly_sys::StringPiece {
/// type Kind = cxx::ExternTypeKindOpaqueCpp;
/// type Id = cxx::type_id!("folly::StringPiece");
/// }
///
/// #[cxx::bridge(namespace = folly)]
Expand All @@ -93,18 +180,34 @@
/// # fn main() {}
/// ```
pub unsafe trait ExternType {
/// The type's kind.
///
/// Must be either:
/// * `ExternTypeKindShared` for a shared type declared outside of an extern block in a
/// cxx::bridge, or
/// * `ExternTypeKindOpqaueCpp` for an opaque C++ type declared inside of an `extern "C"`
/// block.
///
/// Opaque Rust type aliases are unsupported because they can included with a use declaration
/// and aliased more simply outside of the cxx::bridge.
type Kind;

/// A type-level representation of the type's C++ namespace and type name.
///
/// This will always be defined using `type_id!` in the following form:
///
/// ```
/// # struct TypeName;
/// # unsafe impl cxx::ExternType for TypeName {
/// # type Kind = cxx::ExternTypeKindOpaqueCpp;
/// type Id = cxx::type_id!("name::space::of::TypeName");
/// # }
/// ```
type Id;
}

pub struct ExternTypeKindOpaqueCpp;
pub struct ExternTypeKindShared;

#[doc(hidden)]
pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
pub fn verify_extern_type<T: ExternType<Kind = Kind, Id = Id>, Kind, Id>() {}
17 changes: 16 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,21 @@
//! <tr><td><sup><i>tbd</i></sup></td><td>std::shared_ptr&lt;T&gt;</td></tr>
//! </table>
//!
//! <br>
//!
//! # Working with multiple bridge modules
//!
//! Multiple cxx::bridge modules can coexist in the same project. There are multiple reasons for
//! wanting to do so, such as for code organization reasons or to share a common library of FFI
//! types across many different bridges. Type aliases can be used inside the cxx::bridge to safely
//! unify the same opaque C++ type across multiple bridges or to reuse a shared type declared in
//! another bridge.
//
//! See the [documentation for the ExternType trait](trait.ExternType.html) for more information
//! and examples.
//!
//!
//! [ExternType trait]: trait.ExternType.html
//! [https://github.com/dtolnay/cxx]: https://github.com/dtolnay/cxx

#![no_std]
Expand Down Expand Up @@ -395,7 +410,7 @@ mod unwind;
pub use crate::cxx_string::CxxString;
pub use crate::cxx_vector::CxxVector;
pub use crate::exception::Exception;
pub use crate::extern_type::ExternType;
pub use crate::extern_type::{ExternType, ExternTypeKindOpaqueCpp, ExternTypeKindShared};
pub use crate::unique_ptr::UniquePtr;
pub use cxxbridge_macro::bridge;

Expand Down
Loading