-
Notifications
You must be signed in to change notification settings - Fork 315
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
Generate member functions #36
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a quick pass through, I'd like to take another look after the comments have been addressed.
src/bindgen/library.rs
Outdated
@@ -633,8 +634,10 @@ impl Library { | |||
// Gather only the items that we need for this | |||
// `extern "c"` interface | |||
let mut deps = DependencyList::new(); | |||
let mut oop = MemberFunctions::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does oop
stand for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Object orientated programming (It is a bad name, should better be something like member_functions)
src/bindgen/ir/structure.rs
Outdated
out.write("return ::"); | ||
out.write(&f.name); | ||
out.write("("); | ||
let align_lenght = out.line_length_for_align(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'align_length'
@@ -873,6 +914,16 @@ impl GeneratedBindings { | |||
} | |||
|
|||
if self.config.language == Language::Cxx { | |||
out.new_line(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why two new lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To separate those implementations visually from the functions before.
One new line is needed to start a new line after a function definition, the second is inserted to have an empty line between functions and classes.
pub fn add_member_function(&self, out: &mut MemberFunctions) { | ||
if let Some(&(_, ref ty)) = self.args.get(0) { | ||
match *ty { | ||
Type::ConstPtr(ref t) | Type::Ptr(ref t) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to make these member functions const correct.
fn do_something(foo: *const Foo)
Should generate
struct Foo {
void do_something() const {
::do_something(this)
}
}
src/bindgen/ir/structure.rs
Outdated
@@ -230,6 +255,33 @@ impl Source for Struct { | |||
} | |||
} | |||
|
|||
fn format_function_call_1<W: Write>(f: &Function, out: &mut SourceWriter<W>) { | |||
out.write("return ::"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice to omit the return
if the return type is void
, but from my brief testing it isn't a compile warning/error.
src/bindgen/library.rs
Outdated
documentation: s.documentation.clone(), | ||
}; | ||
result.items.push(PathValue::OpaqueItem(opaque)); | ||
s.functions = functions.into_iter() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment mentioning essentially that you're stripping out the this
argument?
src/bindgen/library.rs
Outdated
@@ -758,6 +797,7 @@ pub struct GeneratedBindings { | |||
monomorphs: Monomorphs, | |||
items: Vec<PathValue>, | |||
functions: Vec<Function>, | |||
full_objects: Vec<Struct>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rename this to member_function_structs
? I don't mind the longer name as long as it is clear.
src/bindgen/ir/structure.rs
Outdated
@@ -24,6 +24,7 @@ pub struct Struct { | |||
pub generic_params: Vec<String>, | |||
pub documentation: Documentation, | |||
pub functions: Vec<Function>, | |||
pub destructor: Option<Function>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm worried this could be a potential footgun. In most cases if you implement any of the copy constructor, copy assignment, or destructor, you need to implement them all. See the rule of three.
For example
struct WrapperVec {
data: Vec<u8>,
}
fn destructor(this: *WrapperVec) {
mem::drop(*this)
}
{
WrapperVec a = ...
WrapperVec b = a; // default copy constuctor does a shallow copy of the vec ptr's
} // destructor for a and b runs and the vec is dropped twice leading to a double free
This is a tricky problem. If we were to disable the copy constructor and copy assignment and only allow moves, I think we'd be in a better position. That would disadvantage rust structs that truly are POD. We could maybe generate clone()
methods then?
I think whatever the solution is, it needs to be complete so we can't just generate destructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like my c++ got a bit rusty 😄
What about the following idea:
If there is a destructor the exported struct is likely no POD and does not implement Copy
on rust side. Therefore the copy constructor and copy assignment operator should be disabled. ( A future version may allow them by defining a clone function(like the destructor) that is internally called)
For me this seems like the solution that matches the underlying behavior of the rust code.
I think whatever the solution is, it needs to be complete so we can't just generate destructor.
I've pushed the current implementation to see what others think about the idea. I've developed this while trying to generate a usable api for our internal project. Internally all my functions to free some allocated memory are implemented to reset the struct in a non allocating state, meaning free all memory and then set all pointers to null pointers. So the copy constructor should not harm this implementation. (But this is defined by my implementation, not by the generated header, so this is a not so great solution)
Add a option to generate member function wrappers for each function taking a pointer to a struct type as first argument
If a destructor is implemented it is unsafe to have a copy constructor without having a clone implementation (Future versions may allow to have a copy construtor if a clone method is provieded from rust side)
b729aff
to
506297f
Compare
I've fixed all comments. |
I'm sorry about the inconvenience, but this will probably need a rebase with the changes that have just landed. |
As written in #42 I think it is not possible to implement this with the current dependency management in cbindgen. So we will need to improve this first. Also I've found some additional problems related to the c++(gcc, clang) calling conventions. #[repr(c)]
struct Foo {
a: i32
}
#[no_mangle]
pub extern "C" fn make_foo(a: i32) -> Foo {
Foo {a }
}
#[no_mangle]
pub extern "C" fn some_member(foo: *mut Foo) {} The straight forward C++ header will look like this: struct Foo;
extern "C" void some_member(Foo* foo);
extern "C" struct Foo {
int32_t a;
void some_member() {
::some_member(this);
}
};
extern "C" Foo make_foo(int32_t a); When you try to use this code from some c++ programm, you will notice that something strange happens when you call make_foo. (The value passed will not be equal the value received on rust side). Gcc happily accepts this code 😥 while clang emitts the following warning:
To solve this we need to generate some more wrapping code: struct Foo;
extern "C" void some_member(Foo* foo);
namespace intern {
extern "C" struct Foo_intern {
int32_t a;
};
}
struct Foo: public intern::Foo_intern {
Foo(intern::Foo_intern&& other) {
this->a = other.a;
}
void some_member() {
::some_member(this);
}
};
namespace intern {
extern "C" Foo_intern make_foo(int32_t a);
}
inline Foo make_foo(int32_t a) {
return intern::make_foo(a);
} See this commit for an implementation. The big question now is how to move forward on this? |
So in the future I would like a feature like this. My goal with cbindgen is to make using a rust library from C/C++ safer and more ergonomic (in that order). Right now I don't have a whole lot of time for the library so I've been focusing on smaller things. This feature seems like it'll require a lot of careful thought and will be a significant investment for the codebase so I'm hesitant to merge anything right now. I appreciate the work you've been doing, and would like to see what comes of it. |
As there isn't a clear answer to these C++ and Rust interop/safety questions, I'm going to close this for now. |
As far as I remember all raised questions seems to be solved in my fork. The "biggest" issue was that thing with the calling conventions, that can be solved by generating some wrapper code as shown in this comment. |
This pull request tries to make the generated c++ header more idiomatic.
In detail it tries to generate small member functions wrapping the exported functions for this type.
For example for the following rust code
The following c++ header is generated
In addition to generate more idiomatic code this allows to enforce some invariants of the underlying rust code. For example one could now safely assume that the pointer a for the function
do_something
is not null.Future improvements for this feature may be to generate also constructor and destructor wrappers.
I'm not sure about the syntax of this yet.