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

Introduce new macro to wrap a C++ class: cpp_class! #28

Merged
merged 8 commits into from Jun 7, 2018

Conversation

Projects
None yet
2 participants
@ogoffart
Collaborator

ogoffart commented Jan 20, 2018

A macro which allow to wrap C++ class into rust types with and call
their destructor and copy constructor appropriatly

Example of use:

cpp_class!(pub struct MyClass, "MyClass");
impl MyClass {
    fn new() -> Self {
        unsafe { cpp!([] -> MyClass as "MyClass" { return MyClass(); }) }
    }
    fn member_function(&self, param : i32) -> i32 {
        unsafe { cpp!([self as "const MyClass*", param as "int"] -> i32 as "int" {
            return self->member_function(param);
        }) }
    }
}

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch 2 times, most recently from 3b31ac8 to f653580 Jan 20, 2018

Add cpp_class!
A macro which allow to wrap C++ class into rust types with and call
their destructor and copy constructor appropriatly

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch from f653580 to d217410 Jan 20, 2018

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch from ef103e2 to 26712ab Mar 20, 2018

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch from 26712ab to cef628e Mar 30, 2018

@ogoffart

This comment has been minimized.

Collaborator

ogoffart commented Mar 30, 2018

@mystor Do you have any feedback to give?

I'm slowly developing a crate that uses a C++ library, and uses this feature and the other ones.
Would be nice to have it merged.

@ogoffart

This comment has been minimized.

Collaborator

ogoffart commented Apr 14, 2018

@mystor ping?

@mystor

This comment has been minimized.

Owner

mystor commented Apr 16, 2018

@ogoffart Hey, so sorry for how long this has taken. I'll try to take a look at this in the next few days, but I'm pretty busy with work right now.

@mystor

This looks awesome! Thank you so much for doing this work & sorry for how long it's taken me to get back to you. I'll try to be more prompt with follow-ups.

/// class is copyable (or Copy if it is trivialy copyable), and Default if the class
/// is default constructible
///
/// Warning: This only work if the C++ class can be moved in memory (using

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

nit: Can you make this warning more prominent (perhaps above the example code invocation?). I think it's pretty important :-)

This comment has been minimized.

@ogoffart

ogoffart May 16, 2018

Collaborator

yes, it's pretty important and quite a bad restriction.
There is unfortunately afaik no way to tell the rust compiler that a type should not be moved around, but only copied, or to call the move constructor.

Fortunately, most value types i've been using can be safely moved. Altough this is not the case for types from the standard library such as std::string.

/// is default constructible
///
/// Warning: This only work if the C++ class can be moved in memory (using
/// memcpy). This disallow most classes from the standard library.

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

nit: s/disallow/disallows

///
#[macro_export]
macro_rules! cpp_class {
(struct $name:ident, $type:expr) => {

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

It might be really slick to support this as a mode of operation for the cpp! macro (e.g. cpp! { struct... } or cpp! { pub struct... }), rather than introducing the cpp_class macro.

This comment has been minimized.

@ogoffart

ogoffart May 16, 2018

Collaborator

Thats a good idea.

cpp! { struct Foo as "Foo" }

Note that is was thinking the macro could be also extended to more complex constructs

// This will automatically use operator==
cpp! { #[derive(Eq)] struct Foo as "Foo" }

// map the members to the C++ structure and somehow check it is valid
cpp! { struct Point as "Point" {  x : u32,   y: u32  }  }

This comment has been minimized.

@ogoffart

ogoffart May 17, 2018

Collaborator

Actually, i'm a bit unsure.

having only one macro cpp! could be nice, and would avoid wondering if cpp_class is the right name or if it should be cpp_struct.

However, having a separate macro makes it easier to document or to lookup what it is, than having a single macro that does everything.

hash = hash
));
} else {
sizealign.push(format!("{{
{hash}ull,
sizeof({type}),
rustcpp::AlignOf<{type}>::value
rustcpp::AlignOf<{type}>::value,
rustcpp::Flags<{type}>::value

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

Perhaps this could be made cleaner by unifying all of this logic into a single ::Flags-like invocation. We could just have a list of rustcpp::Meta<{type}, {hash}ull>::value?

This comment has been minimized.

@ogoffart

ogoffart May 16, 2018

Collaborator

I'm not sure I understand what you mean.
You mean that rustcpp::Meta<{type}, {hash}ull>::value would be a SizeAlign object?

This comment has been minimized.

@mystor

mystor May 17, 2018

Owner

Yeah, that was my thought.

This comment has been minimized.

@ogoffart

ogoffart May 24, 2018

Collaborator

This needs C++11's constexpr, so it depends on the other change that enables C++11

This comment has been minimized.

@ogoffart

ogoffart Jun 5, 2018

Collaborator

Note that MSVC 2013 which is still tested in the CI doesn't support constexpr enough to get this to work.

extern "C" {{
void __cpp_destructor_{hash}(void *ptr) {{
typedef {cpp_name} T;
static_cast< {cpp_name} *>(ptr)->~T();

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

Why use static_cast<{cpp_name} *> here instead of just casting to T*?

}

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

nit: extra whitespace

#[test]
fn move_only() {
cpp_class!(struct MoveOnly, "MoveOnly");

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

nit: perhaps use as instead of the comma in cpp_class! to make it more consistent with how the type is usually used?

output,
r#"
extern "C" {{
void __cpp_destructor_{hash}(void *ptr) {{

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

It's unfortunate that we define these no-op functions even if we will never use them (because they aren't available). :-/ Not much that can be done about that AFAIK though.

This comment has been minimized.

@ogoffart

ogoffart May 17, 2018

Collaborator

For the destructor and copy constructor, we can't do anything.

For the default constructor, I was considering making it explicit by having to write
#[derive(Default)] in the macro.

I am actually undecided if it is good to have it explicit or implicit.

Implicit is quite convinient : if the C++ has a default constructor, it is automatic. But the rust rules seems to prefer explicit for this kinf of things.

But then what about all the other trait, should we automatically derive PartialEq or PartialOrd if the operator exists?

typename enable_if<is_copy_constructible<T>::value>::type copy_helper(const void *src, void *dest)
{ new (dest) T (*static_cast<T const*>(src)); }
template<typename T>
typename enable_if<!is_copy_constructible<T>::value>::type copy_helper(const void *, void *) { }

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

Perhaps make this code path abort() the process, just to double-down on the "this should never be called" angle?

@@ -45,6 +45,50 @@ const INTERNAL_CPP_STRUCTS: &'static str = r#"
#include "stdint.h" // For {u}intN_t
#include <new> // For placement new
#if __cplusplus <= 199711L && !defined(_MSC_VER)

This comment has been minimized.

@mystor

mystor May 16, 2018

Owner

I don't think we support C++ < 11 right now.

@ogoffart

This comment has been minimized.

Collaborator

ogoffart commented May 24, 2018

I made most change, but I did not rename the macro to re-use cpp.

I think it is better to be implicit about the difference of usage of the macro.
That makes the code more readable and lets the user find the documentation for the relevant macro more easily. Is this also not a reason why there are no overloaded functions in rust?
This is not a strong opinion. What do you think?

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch 3 times, most recently from 2a3dc17 to c5a61ba May 24, 2018

Use #line to report the error at the cpp_class location
For example if there is a typo in the C++ class name, or if the class
cannot be destructed or is only forward declared.

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch from 1de8d9e to aa1484a Jun 5, 2018

Use C++11 unconditionally
Note that some type traits are still not implemented in old GCC

@ogoffart ogoffart force-pushed the ogoffart:cpp_class branch from aa1484a to 44791f4 Jun 5, 2018

@mystor

This comment has been minimized.

Owner

mystor commented Jun 7, 2018

Thanks for the changes :-) Looks good now.

@mystor mystor merged commit e727d74 into mystor:master Jun 7, 2018

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

mystor added a commit that referenced this pull request Jun 7, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment