Opaque C++ types
Opaque borrowed C++ type
The generated Rust code contains a ZngurCppOpaqueBorrowedObject
which you can use to create opaque Rust equivalent for C++ types.
To do that, you first need to create a newtype wrapper around it in Rust:
struct Way(generated::ZngurCppOpaqueBorrowedObject);
Then you need to add this type to your main.zng as a #cpp_ref:
type crate::Way {
#cpp_ref "::osmium::Way";
}
Note that #cpp_ref types don't need manual layout policy.
This enables creating rust::Ref<rust::crate::Way> from a const osmium::Way& in C++
and you can pass it to the Rust side.
Rust side can't do anything meaningful with it, except passing it again to the C++ side.
In the C++ side rust::Ref<rust::crate::Way> has a .cpp() method
which will return the osmium::Way& back to you.
If you want to use the methods on your C++ type in the Rust side,
you can write impl and impl trait blocks for the newtype wrapper crate::Way inside C++.
See the examples/osmium for a full working example.
Opaque owned C++ type
Owning C++ types on the Rust stack is impossible without a huge amount of acrobatics,
since Rust assumes that every type is memcpy-movable,
which doesn't work for C++ types with non-trivial move constructors.
It is not entirely impossible,
for example the moveit crate achieves it by hiding the binding of the stack owner in a macro:
moveit! {
let mut stack_obj = ffi::A::new();
}
// here, type of `stack_obj` is `Pin<MoveRef<ffi::A>>`, not `ffi::A` itself.
stack_obj.as_mut().set(42);
assert_eq!(stack_obj.get(), 42);
Keeping the Rust side clean and idiomatic is one of the design goals of Zngur, and such a macro is not clean and idiomatic Rust. So, storing C++ objects on the Rust stack is not supported. If you really need to store things on the Rust stack, consider moving the type definition into Rust.
Keeping C++ objects in Rust using heap allocation
is supported with ZngurCppOpaqueOwnedObject.
Creating trait objects from C++ types
By the above infrastructure you can convert your C++ types into &dyn Trait or Box<dyn Trait>.
To do that, you need to:
- Create an opaque borrowed (or owned for
Box<dyn Trait>) type for the C++ type. - Implement the
Traitfor that type inside C++. - Cast
&Opaqueto&dyn Traitwhen needed.
There is a shortcut provided by Zngur.
You can define the trait in your main.zng:
trait iter::Iterator::<Item = i32> {
fn next(&mut self) -> ::std::option::Option<i32>;
}
and inherit in your C++ type from it:
template <typename T>
class VectorIterator : public rust::std::iter::Iterator<T> {
std::vector<T> vec;
size_t pos;
public:
VectorIterator(std::vector<T> &&v) : vec(v), pos(0) {}
Option<T> next() override {
if (pos >= vec.size()) {
return Option<T>::None();
}
T value = vec[pos++];
return Option<T>::Some(value);
}
};
Then you can construct a rust::Box<rust::Dyn> or rust::Ref<rust::Dyn> from it.
auto vec_as_iter = rust::Box<rust::Dyn<rust::std::iter::Iterator<int32_t>>>::make_box<
VectorIterator<int32_t>>(std::move(vec));
If you need to call the trait methods on the result, you need to add a dyn Trait or Box<dyn Trait> in your zng file as well:
trait iter::Iterator::<Item = i32> {
fn next(&mut self) -> ::std::option::Option<i32>;
}
type dyn iter::Iterator::<Item = i32> {
wellknown_traits(?Sized);
fn next(&mut self) -> ::std::option::Option<i32>;
fn map<i32, Box<dyn Fn(i32) -> i32>>(self, Box<dyn Fn(i32) -> i32>)
-> ::std::iter::Map<::std::vec::IntoIter<i32>, Box<dyn Fn(i32) -> i32>>;
}
type Box<dyn iter::Iterator<Item = i32>> {
#layout(size = 16, align = 8);
fn deref(&self) -> &dyn dyn iter::Iterator<Item = i32> use ::core::ops::Deref;
fn collect<::std::vec::Vec<i32>>(self) -> ::std::vec::Vec<i32>;
}
Now you can call collect and map on the resulting iterator defined in C++.
Note that you don't need the trait declaration in the zng file if you just need working with trait objects exposed from Rust code.
In that case, just declaring the type dyn Trait is enough, and it works like any other type.
The trait declaration in the zng file is only needed if you want to use this feature.
Semantics of the opaque types
The ZngurCppOpaqueBorrowedObject and newtype wrappers around it don't represent a C++ object,
but they represent an imaginary ZST Rust object at the first byte of a C++ object.
This can sometimes cause behavior that is safe and sound,
but surprising and counterintuitive for someone that expects them to represent the whole C++ object.
Some examples (assume RustType is a newtype wrapper around ZngurCppOpaqueBorrowedObject
that refers to a CppType class in the C++):
std::mem::sizeof::<RustType>()is 0, not the size ofCppTypestd::mem::alignof::<RustType>()is 1, not the align ofCppTypestd::mem::swap::<RustType>(a, b)only swaps the first zero bytes of those, i.e. does nothing.
Those problem might be solved by the extern type language feature.
Using that, we can define the ZngurCppOpaqueBorrowedObject as a proper extern type
instead of an imaginary zero sized type at the first of C++ objects.