Comments (17)
I've been using these crates for wrapping some C++ libraries I need to use from Rust. This library has been the easiest, lowest anxiety way to achieve it, so thanks very much for your work so far.
Just to provide a use case for this, I end up with a certain pattern a lot when Rust ends up with ownership of C++ object:
pub struct CPlusPlusBox {
payload: *mut c_void,
}
impl CPlusPlusBox {
pub fn new(...) -> CPlusPlusBox {
unsafe {
result = cpp!([...] -> *mut c_void as "MyCPlusPlusType*" {
return new MyCPlusPlusType(...);
});
}
result
}
}
impl Drop for CPlusPlusBox {
fn drop(&mut self) {
let payload = self.payload;
unsafe {
cpp!([payload as "MyCPlusPlusType*"] {
delete payload;
});
}
}
}
I was hoping I could factor out some of this boilerplate using a macro (or perhaps a custom derive might make more sense in this particular case). In general, allowing for macros to generate cpp! blocks could make writing wrappers less boilerplate heavy giving as a sort of middle way between the current situation (more manual work, lots of control, no problem with using all C++ features) and bindgen (more automation, less control, confusing to deal with templates).
I'm a still more or less Rust neophyte (for example, I haven't yet actually even written a macro myself - this is the first time I've felt the need) and might be out of my depth here, and perhaps I don't fully understand the issues completely, but I think perhaps I might have a potential slightly slow/hacky way this could maybe be implemented reasonably easily:
- First somehow replace/monkeypatch the cpp![]{} macro with a no-op, for example by playing with the paths the Rust compiler looks in for sources.
- Do an initial macro expansion of each Rust file before looking for cpp! blocks using "rustc --pretty expanded".
- Put the actual cpp![]{} macro back, and proceed as normal.
Might something like this work? One immediate problem that jumps out here is error messages and keeping track of line numbers might become challenging. In particular, even if it's possible to retrieve the pre-expansion line numbers, what about when there's an error in some C++ generated by a macro. How can it be attributed to the correct macro expansion? Taking a preliminary look, it's clear you've worked quite hard to get proper line numbering so I guess this is definitely something which needs to be addressed properly.
from rust-cpp.
Asserts can be handled by generating a companion global struct for each c++ closure, which would store size/alignment of all of its arguments.
The tricky bit seems to be knowing when to invoke the c++ compiler. Proc-macro crate interface currently has no provisions for rustc notifying the macro crate that it is done with macro expansion. This could be worked around, though, by asking users to place generate_cpp!()
macro as last thing in crate's main file (which would expand to a custom derive from cpp_macros). I think it's pretty safe to assume that such a macro will be expanded last.
In the future, when proc_macro_attribute
is stable, this could become simply a crate-level attribute.
from rust-cpp.
We'd need more APIs and ways to interact with the compiler. Currently we use a build script to collect all header includes and individual cpp! calls into C++ code which is built and linked with the rust code, and then during the rust compilation those cpp! calls are converted into actual FFI calls into that pre-built C++ code.
We'd need to be able to generate that C++ code during macro expansion while the rustc compiler is running, and hook into the compiler before the link step in order to compile and link the C++ code into a final library.
Right now we also take advantage of the fact that we've built all of the C++ code before we start compiling rust code by making the rust procedural macro insert assertions that object size and alignment matches across the FFI boundary, and that would be much trickier to do if we can't do the pre-build work we're doing right now.
It might be possible if we don't care about invoking the C++ compiler once for each cpp! closure invocation. (all header includes are parsed in the build script but the actual code being parsed by the invocation), but I'm not sure.
All in all, it's a lot of work with tradeoffs, but it might be doable.
from rust-cpp.
Unfortunately, I don't think that you can make custom crate-level attributes with proc_macro_attribute, as you can't import the macro to use it before the attribute needs to be resolved. I think there used to be an ICE which was caused by trying that, which was fixed by simply disabling it. I might be wrong / things may have changed since then though.
I'm not comfortable depending on the order of macro expansion right now, especially because there's nothing technically stopping rustc from performing parallel macro expansion, which may become a thing which we do in the future for performance reasons, especially if people start doing a lot of complex work in procedural macros.
In general I think the easiest way to handle code which is generated by macros would be to try to discover all of the files ahead of time & build them, like today, but have a fallback where each individual macro which wasn't discovered is built separately if you're using macros, and we link in all of the many small crates. I think it might work OK, but we wouldn't be able to support expanding includes etc. from of macros. It might end up being a thing which is only enabled if you enable a feature.
We already generate a global struct for each c++ closure which stores size/alignment of all arguments - we just check it at compile time instead of at run time to try to avoid the performance overhead of actually performing the asserts at runtime when they are trivially true.
from rust-cpp.
#35 will help in some cases (although it won't help for the CPlusPlusBox use case, but that's covered by cpp_class! )
from rust-cpp.
What if build script used rustc
itself to parse the crate? (with a different implementation of the cpp! macro to collect c++ code).
Additionally, I am thinking that rust-cpp might be able to get rid of as "<c++ type>"
annotations if we could find a way to embed type metadata into rlib generated in this step.
Perhaps something like this:
pub trait CPPTypeInfo {
const CPP_TYPE: &'static str;
const SIZE: usize;
}
impl CPPTypeInfo for i32 {
const CPP_TYPE: &'static str = "int32_t";
const SIZE: usize = size_of::<i32>();
}
impl CPPTypeInfo for f32 {
const CPP_TYPE: &'static str = "float";
const SIZE: usize = size_of::<f32>();
}
#[repr(C)]
pub struct Metadata {
magic: u64,
type_info: &'static str,
size: usize,
}
pub fn emit_type_info<T: TypeInfo>(x: &T) {
let m = Metadata {
magic: MAGIC,
type_info: T::CPP_TYPE,
size: T::SIZE,
};
blackbox(&m); // can to external function - to make sure this is not optimized out
}
Each captured variable in a cpp! block would be expanded to emit_type_info(&var)
, and afterwards build script would search generated .rlib for metadata, in a similar manner as it does now with the lib generated by c++.
This would make build slower, of course, but the added convenience might be worth it...
from rust-cpp.
What if build script used rustc itself to parse the crate?
Then it would only work with a nightly compiler, and never with stable.
So I don't think that is an option.
It is true that, in that case, we could use the type information from rustc to avoid redundant annotations.
We could do something similar as what you describe with the existing procedural macro. But then we would need a way to build the C++ after rust.
Cargo's current build.rs is always run before, i don't know if there are scripts we can run after.
Running the compiler once for every cpp! macro would also work, but i'd rather not as it would make the compilation really really slow. (maybe with pre-compiled header we can mitigate that, but still)
from rust-cpp.
Then it would only work with a nightly compiler, and never with stable.
Why? I didn't mean to use libsyntax or anything like that, but rather spawn rustc as an external process.
We would need two versions of the cpp_macros crate:
- cpp_macros_pre, that saves cpp! snippets to a file and replaces them with calls to
emit_type_info
for each captured variable and the return type. - cpp_macros, that is the same as current cpp_macros
The build sequence would be as follows:
- cargo builds and spawns the build script
- build script spawns rustc passing it
--crate_type=rlib --crate-name=crate_pre --extern cpp_macros=cpp_macros_pre
flags - rustc builds the crate and emits crate_pre.rlib
- build script parses metadata from crate_pre.rlib, combines with c++ snippets, emits c++ source, invokes the c++ compiler.
- cargo invokes rustc for the second time, pointing it to the "normal" cpp_macros, which works the same as today.
Obviously, this builds the crate twice, although if we didn't want to eliminate annotations, it could be sped up by not running codegen via --emit=metadata
.
I am glossing over a lot of details, such as how to discover the rest of rustc command line parameters required to build crate_pre, but I figure this can be solved one way or another.
from rust-cpp.
Hey. Was there any progress on this? I'm in the same situation at the moment.
from rust-cpp.
I suppose there's not been any progress. Although I haven't used it myself, one thing to check out --- for comparisons sake if nothing else --- is Google's cxx and autocxx. These appear to have some kind of tools and approach for dealing with ownership between C++ and Rust.
from rust-cpp.
@frankier: the cxx
crate has the same problem.
It also uses a build script that parses the .rs files to extract some information before building the generated .cpp code with a C++ compiler. the #[cxx::bridge]
module can't be generated from another macro.
from rust-cpp.
the cxx crate has the same problem.
@ogoffart it doesn't, at least not in the context of #19 (comment). They are interested in using a macro to generate a CPlusPlusBox
Rust type which is a new/delete smart pointer around a C++ MyCPlusPlusType
. With the CXX library they'd idiomatically use cxx::UniquePtr<MyCPlusPlusType>
and not need a macro in the first place.
from rust-cpp.
This crate equivalent of CPlusPlusBox
is to do
cpp_class!(unsafe struct MyCPlusPlusType as "std::unique_ptr<MyCPlusPlusType>");
(that did not exist in 2017 when that comment was written)
But this issue is about embedding cpp!
in other macros, so my comment was addressing that.
The ownership problem can be dealt with cpp_class! without the use of cpp! within macro.
from rust-cpp.
Thanks to both of you for the info. It'll be very useful for next time I'm trying to wrap C++ with Rust. Sorry for derailing the issue!
from rust-cpp.
I just mean that collecting why people reach for macros and solving those use cases in a better way is a viable way to make significant progress on this issue.
The ownership problem can be dealt with cpp_class! without the use of cpp! within macro.
I'm not convinced of this. A cpp_class with Default and Drop isn't all that goes into a smart pointer binding. You'd be back in macro land for something with as_ref
, into_raw
, new
, supporting Deref
to call methods on the inner type, Debug
where the inner type implements Debug, etc.
from rust-cpp.
true, cpp_class wrapping an unique_ptr is just box an opaque type, and there is no way to access the content. So this is usefull when one wants to expose a type that can't be move as its boxed variant, and add associated function on the boxed type itself to expose more functions of that type.
from rust-cpp.
I found a solution for this. I use cpprs
to process C macros that generate cpp!(...)
macros, which can then be processed by rust-cpp
in the usual way.
It's not ideal, but it's better and more maintainable than cut-and-pasting. I'm tempted to write something that can expand a subset of Rust's macros with an annotation for this purpose, but this will do for now.
from rust-cpp.
Related Issues (20)
- Using cpp! macro in integration tests HOT 1
- Nothing but praise HOT 1
- undefined reference to `std::__throw_bad_array_new_length()'
- Adding docs to cpp_class! generated classes HOT 2
- Link error on MacOS arm64 HOT 3
- Updating aho-corasick HOT 1
- Document how exceptions are handled HOT 3
- Easier conversion from `cc::Build` to `cpp_build::Config` ? HOT 1
- [Question] include local some.hpp HOT 1
- Using elements defined in `cpp!` macro in other cpp files
- duplicate typeinfo for templates HOT 2
- Submodule skipping `mod.rs`, not found by `cpp_build`. HOT 1
- Metadata parsing assumes little endian, even for big endian targets HOT 1
- Align and size assertion failures HOT 7
- Feature request: support attributes in rust! pseudo-macro
- Feature request: compile time constexpr in Rust via cpp!() HOT 1
- Extra blanks in generated cpp_closures.cpp HOT 4
- Make the cpp! macro a real proc macro HOT 2
- Error while Wrapping a C++ Class HOT 5
- Many clippy warnings when running on code containing `cpp!` macro HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rust-cpp.