-
Notifications
You must be signed in to change notification settings - Fork 338
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
Provide builtin handling of exposing a C++ constructor #280
Comments
I would totally love it |
The prospect that I am interested in exploring with this is whether we can let writing a cxx::ExternType impl give you complete free reign with a type, as if it were defined natively in Rust all along. It wasn't satisfying to say we just can't let you call the constructor because "your type might have a move constructor" and "we don't parse your header so we don't know the size of your type" when the situation is "but it doesn't" and "but I can promise (unsafely if necessary) that it's N bytes". On the UniquePtr one, I am not fully sold at this point but I can see that it's extremely convenient, so I've mostly come around and would accept a PR on it -- but I'd like it to be after I finally get around to reviewing the |
This issue states that it's safe to make a constructor, which returns a type by value, for a type without a move constructor. i.e. fn new(arg: &str) -> ZeusClient; // will verify there is no move constructor I don't think that's exactly right - counterexample: #include <string>
class Contained {
public:
std::string a;
uint16_t b;
};
class ZeusClient {
public:
Contained a;
}; Anything containing a But, I assume your plans for static assertions here involve something like #include <iostream>
#include <string>
#include <type_traits>
class SafeStruct {
public:
uint16_t a, b;
};
class SafeContainer {
public:
SafeStruct a;
};
class Contained {
public:
std::string a;
uint16_t b;
};
class ZeusClient {
public:
Contained a;
};
int main(void) {
std::cout << "uint16_t " << std::is_trivially_move_assignable<uint16_t>::value << std::endl;
std::cout << "string " << std::is_trivially_move_assignable<std::string>::value << std::endl;
std::cout << "SafeStruct " << std::is_trivially_move_assignable<SafeStruct>::value << std::endl;
std::cout << "SafeContainer " << std::is_trivially_move_assignable<SafeContainer>::value << std::endl;
std::cout << "Contained " << std::is_trivially_move_assignable<Contained>::value << std::endl;
std::cout << "ZeusClient " << std::is_trivially_move_assignable<ZeusClient>::value << std::endl;
return 0;
} which prints:
I have not looked up the C++ spec to find out if "trivially move assignable" means what it sounds like it means. I assume this is what you meant all along anyway, but as I dug into it a little, I thought I ought to post my notes here. |
Thanks for clarifying -- yeah that's what I had in mind, "no move constructor" ≈≈ "no user-defined move constructor or nontrivial implicitly defined move constructor". Rather than Here is a counterexample which is trivially_move_assignable but would be unsound to move in Rust: struct Struct {
Struct();
~Struct();
};
static Struct Default;
static Struct *Ptr = &Default; // invariant: always points to valid struct!!
Struct::Struct() { Ptr = this; }
Struct::~Struct() { Ptr = &Default; } |
People may have already found this but note that C++ templates can be used so that methods don't need to be created for every constructor of a C++ type. Eg template<typename T, typename... Args>
T
construct(Args... args)
{
return T(args...);
} Could be used then with Obviously you also need to ensure that the But this greatly reduces the C++ code if you need to define many constructors or similar methods for types and can sortof be used as a workaround until CXX does have proper a way of describing constructors/deconstructors without the C++ glue. |
To the extent that constructors "return" a C++ type by value, they're not translatable to Rust because Rust moves (memcpy) are incompatible with C++ moves (which can require a move constructor to be called). Translating an arbitrary constructor to
fn new() -> Self
would not be correct.The current workarounds are:
include!
a shim which does the construction behind a unique_ptr or similar.That last approach would look something like:
However, it would be very possible for us to handle at least the special case of types without a move constructor. In the following snippet, CXX would need to: recognize the
new
function name since that isn't otherwise a legal C++ function name, identify the Self type based on the extern type in return position of the function signature, statically verify there is no move constructor, and emit the constructor as an associated functionZeusClient::new
to Rust.Additionally we can consider supporting implicit UniquePtr construction of types which may or may not have a move constructor, with or without an ExternType impl.
The text was updated successfully, but these errors were encountered: