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

Enable sharing a consistent Rust type across multiple FFI blocks #190

Merged
merged 16 commits into from
May 8, 2020
Merged

Conversation

dtolnay
Copy link
Owner

@dtolnay dtolnay commented May 8, 2020

Fixes #99.

This PR introduces a trait cxx::ExternType documented as follows.


A type for which the layout is determined by its C++ definition.

This trait serves the following two related purposes.

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 Demos 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.

// file1.rs
#[cxx::bridge(namespace = example)]
pub mod ffi {
    extern "C" {
        type Demo;

        fn create_demo() -> UniquePtr<Demo>;
    }
}

// file2.rs
#[cxx::bridge(namespace = example)]
pub mod ffi {
    extern "C" {
        type Demo = crate::file1::ffi::Demo;

        fn take_ref_demo(demo: &Demo);
    }
}

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.

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.

mod folly_sys;  // the bindgen-generated bindings

use cxx::{type_id, ExternType};

unsafe impl ExternType for folly_sys::StringPiece {
    type Id = type_id!("folly::StringPiece");
}

#[cxx::bridge(namespace = folly)]
pub mod ffi {
    extern "C" {
        include!("rust_cxx_bindings.h");

        type StringPiece = crate::folly_sys::StringPiece;

        fn print_string_piece(s: &StringPiece);
    }
}

// Now if we construct a StringPiece or obtain one through one
// of the bindgen-generated signatures, we are able to pass it
// along to ffi::print_string_piece.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multiple FFI blocks referencing types from one another
1 participant