diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 29746f6..f06f5ff 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -11,10 +11,8 @@ - [Panic and exceptions](./call_rust_from_cpp/panic_and_exceptions.md) - [Calling C++ from Rust](./call_cpp_from_rust/index.md) - [Calling C++ free functions](./call_cpp_from_rust/function.md) - - [Adding methods to Rust types](./call_cpp_from_rust/rust_impl.md) - - [Implementing Rust traits for Rust types](./call_cpp_from_rust/rust_impl_trait.md) + - [Writing `impl` blocks for Rust types in C++](./call_cpp_from_rust/rust_impl.md) - [`Box`]() - - [Converting C++ objects to `Box`]() - [`Opaque C++ types`](./call_cpp_from_rust/opaque.md) - [Safety](./safety.md) - [How it compares to other tools](./how_it_compares.md) diff --git a/book/src/call_cpp_from_rust/opaque.md b/book/src/call_cpp_from_rust/opaque.md index cae193b..803540d 100644 --- a/book/src/call_cpp_from_rust/opaque.md +++ b/book/src/call_cpp_from_rust/opaque.md @@ -43,6 +43,50 @@ objects in the Rust stack is not supported. If you really need to store things i Keeping C++ object 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`. To do that, you need to: + +- Create a opaque borrowed (or owned for `Box`) type for the C++ type. +- Implement the `Trait` for that type inside C++. +- Cast `&Opaque` to `&dyn Trait` when needed. + +There is a shortcut provided by Zngur. You can define the trait in your `main.zng`: + +``` +trait iter::Iterator:: { + fn next(&mut self) -> ::std::option::Option; +} +``` + +and inherit in your C++ type from it: + +``` +template +class VectorIterator : public rust::std::iter::Iterator { + std::vector vec; + size_t pos; + +public: + VectorIterator(std::vector &&v) : vec(v), pos(0) {} + + Option next() override { + if (pos >= vec.size()) { + return Option::None(); + } + T value = vec[pos++]; + return Option::Some(value); + } +}; +``` + +Then you can construct a `rust::Box` or `rust::Ref` from it. + +``` +auto vec_as_iter = rust::Box>>::make_box< + VectorIterator>(std::move(vec)); +``` + ## 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 @@ -54,4 +98,5 @@ to represent the whole C++ object. Some examples (assume `RustType` is a newtype - `std::mem::alignof::()` is 1, not the align of `CppType` - `std::mem::swap::(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. +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. diff --git a/book/src/call_cpp_from_rust/rust_impl.md b/book/src/call_cpp_from_rust/rust_impl.md index 56c3a8f..12159df 100644 --- a/book/src/call_cpp_from_rust/rust_impl.md +++ b/book/src/call_cpp_from_rust/rust_impl.md @@ -1,6 +1,8 @@ -# Adding methods to Rust types +# Writing `impl` block for Rust types in C++ -You can write `impl` blocks for type defined in your crate (this is not a Zngur restriction, it's the orphan rule) in C++. First, you need to +## Inherent `impl` + +You can write inherent `impl` blocks for type defined in your crate (this is not a Zngur restriction, it's the orphan rule) in C++. First, you need to declare the functions of that block in the `main.zng` file: ```Rust @@ -33,3 +35,41 @@ rust::Impl::get_value_by_key(rust::Ref self, // Your code here } ``` + +## Implementing Rust traits for Rust types + +You can write `impl Trait for Type` blocks for type defined in your crate or traits defined in your +crate (this is not a Zngur restriction, it's the orphan rule) in C++. First, you need to +declare the functions of that block in the `main.zng` file: + +```Rust +extern "C++" { + impl ::std::ops::Index for crate::WayNodeList { + fn index(&self, usize) -> &crate::Node; + } +} +``` + +Then, some class like this will be generated in the `generated.h` file: + +```C++ +namespace rust { +template <> +class Impl<::rust::crate::WayNodeList, + ::rust::std::ops::Index<::size_t, ::rust::crate::Node>> { +public: + static ::rust::Ref<::rust::crate::Node> + index(::rust::Ref<::rust::crate::WayNodeList> self, ::size_t i0); +}; +} +``` + +And you need to implement that in a `.cpp` file, and link it to the crate: + +```C++ +rust::Ref +rust::Impl>::index( + rust::Ref self, size_t i) { + // Your code here +} +``` diff --git a/book/src/call_cpp_from_rust/rust_impl_trait.md b/book/src/call_cpp_from_rust/rust_impl_trait.md deleted file mode 100644 index 421f367..0000000 --- a/book/src/call_cpp_from_rust/rust_impl_trait.md +++ /dev/null @@ -1,36 +0,0 @@ -# Implementing Rust traits for Rust types - -You can write `impl Trait for Type` blocks for type defined in your crate (this is not a Zngur restriction, it's the orphan rule) in C++. First, you need to -declare the functions of that block in the `main.zng` file: - -```Rust -extern "C++" { - impl ::std::ops::Index for crate::WayNodeList { - fn index(&self, usize) -> &crate::Node; - } -} -``` - -Then, some class like this will be generated in the `generated.h` file: - -```C++ -namespace rust { -template <> -class Impl<::rust::crate::WayNodeList, - ::rust::std::ops::Index<::size_t, ::rust::crate::Node>> { -public: - static ::rust::Ref<::rust::crate::Node> - index(::rust::Ref<::rust::crate::WayNodeList> self, ::size_t i0); -}; -} -``` - -And you need to implement that in a `.cpp` file, and link it to the crate: - -```C++ -rust::Ref -rust::Impl>::index( - rust::Ref self, size_t i) { - // Your code here -} -``` diff --git a/book/src/how_it_compares.md b/book/src/how_it_compares.md index 38b1811..83436d1 100644 --- a/book/src/how_it_compares.md +++ b/book/src/how_it_compares.md @@ -29,12 +29,13 @@ for your C++ type, and then convert that type into a `Box` and use it - `CxxString`: - Convert it to a Rust `&CStr`, `&[u8]`, or `&str` if ownership is not required. - Copy it into a Rust `CString`, `Vec`, or `String` if the performance cost is acceptable. - - Write a trait for functionalities you need from it and convert the string to `Box` + - Write a trait for functionalities you need from it and convert the string to `Box`. + - Write an opaque type `CxxString` and implement the functionalities you need in `impl`. - `CxxVector`: - Similar to `CxxString` - `CxxVector`: - Copy it into a Rust `Vec>` if the performance cost is acceptable. - - Write a trait and wrap it like a separate opaque type, which has a `get(usize) -> &dyn Trait` method. + - Write two opaque types `VectorFoo` and `Foo`, where `VecFoo` has a `get(usize) -> &Foo` method. ## AutoCXX diff --git a/book/src/philosophy.md b/book/src/philosophy.md index 08b4d5d..e985e7c 100644 --- a/book/src/philosophy.md +++ b/book/src/philosophy.md @@ -10,12 +10,24 @@ harder than what Rust actually is, creates a not really great first Rust experie Writing glue code in C++ also makes things considerably easier, since C++ semantics are a superset of Rust semantics (See [idea](./zngur.md#idea)) and Rust can't express all C++ things easily. +### Keep `main.zng` in a separate file + +CXX style embedding of the IDL in a Rust proc macro confused Rust newcomers, so Zngur avoids it. + ## Be a zero cost abstraction When Rust and C++ are used in a project, it means that performance is a requirement. So, unlike interoperability tools between Rust and higher level languages, Zngur is not allowed to do deep copies or invisible allocations. -## Don't special case standard library types +## Be build system agnostic + +C/C++ build systems are complex, each in a different way. To support all of them, Zngur doesn't integrate with +any of them. Any build system that can do the following process is able to build a Zngur project: + +- Running `zngur g main.zng` +- Building the Rust project (e.g. by running the `cargo build`) +- Build the C++ `generated.cpp` together with the rest of codes +- Link all together ## Keep Rust things Rusty @@ -24,6 +36,14 @@ tools between Rust and higher level languages, Zngur is not allowed to do deep c ### `Result` is not automatically converted to exception +`Result` has some benefits over exception based error handling. For example, the unhappy case can not be forgotten +and must be handled. Due these benefits, a similar `std::expected` is added to the C++23. In order to not losing +this Rust benefit, `Result` is not converted to a C++ exception. + +Panics, which are implemented by stack unwinding similar to C++ exceptions, are converted to a C++ exception with +the [`#convert_panic_to_exception`](./call_rust_from_cpp/panic_and_exceptions.md) flag. So if you quickly want an exception +out of a `Result`, you can use `.unwrap()`. + ### Copy constructors are deleted, manual `.clone()` should be used Implicit copy constructors are a source of accidental performance cost, and complicate the control flow of program. Rust doesn't support @@ -62,7 +82,8 @@ if (reserve_capacity) { ``` If `Vec v` used the default constructor, it would be a waste call to itself and a wasted call to the drop code -executed immediately after that. Rust also support this, but checks the initialization before usage, which Zngur can't check. +executed immediately after that. Rust also support this, but checks the initialization before usage, which Zngur can't check in the +compile time, but will check in the run time by default. ### Rust functions returning `()` return `rust::Unit` in C++ instead of `void` diff --git a/zngur-cli/Cargo.toml b/zngur-cli/Cargo.toml index f8c9972..eb843f1 100644 --- a/zngur-cli/Cargo.toml +++ b/zngur-cli/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "zngur-cli" description = "CLI of the Zngur, a Rust/C++ interoperability tool" +readme = "../README.md" version = "0.3.0" edition.workspace = true license.workspace = true diff --git a/zngur-def/Cargo.toml b/zngur-def/Cargo.toml index be5bcf0..8184f54 100644 --- a/zngur-def/Cargo.toml +++ b/zngur-def/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "zngur-def" description = "Data types that define the structure of a zng file" +readme = "../README.md" version = "0.3.0" edition.workspace = true license.workspace = true diff --git a/zngur-generator/Cargo.toml b/zngur-generator/Cargo.toml index 4c6de74..ac32d16 100644 --- a/zngur-generator/Cargo.toml +++ b/zngur-generator/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "zngur-generator" description = "Generates Rust and C++ glue codes from the zng file" +readme = "../README.md" version = "0.3.0" edition.workspace = true license.workspace = true diff --git a/zngur-parser/Cargo.toml b/zngur-parser/Cargo.toml index 4dd3e11..5a64094 100644 --- a/zngur-parser/Cargo.toml +++ b/zngur-parser/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "zngur-parser" description = "Parser of the zng file" +readme = "../README.md" version = "0.3.0" edition.workspace = true license.workspace = true diff --git a/zngur/Cargo.toml b/zngur/Cargo.toml index 835ac77..f0a7f91 100644 --- a/zngur/Cargo.toml +++ b/zngur/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "zngur" description = "A Rust/C++ interoperability tool" +readme = "../README.md" version = "0.3.0" edition.workspace = true license.workspace = true diff --git a/zngur/src/lib.rs b/zngur/src/lib.rs index 3545520..edc6c9b 100644 --- a/zngur/src/lib.rs +++ b/zngur/src/lib.rs @@ -1,3 +1,6 @@ +//! This crate contains an API for using the Zngur code generator inside build scripts. For more information +//! about the Zngur itself, see [the documentation](https://hkalbasi.github.io/zngur). + use std::{ fs::File, io::Write, @@ -7,6 +10,18 @@ use std::{ use zngur_generator::{ParsedZngFile, ZngurGenerator}; #[must_use] +/// Builder for the Zngur generator. +/// +/// Usage: +/// ```ignore +/// let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); +/// let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); +/// Zngur::from_zng_file(crate_dir.join("main.zng")) +/// .with_cpp_file(out_dir.join("generated.cpp")) +/// .with_h_file(out_dir.join("generated.h")) +/// .with_rs_file(out_dir.join("generated.rs")) +/// .generate(); +/// ``` pub struct Zngur { zng_file: PathBuf, h_file_path: Option,