Skip to content

Commit

Permalink
Improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
HKalbasi committed Oct 7, 2023
1 parent d16ccaf commit 41082c7
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 46 deletions.
4 changes: 1 addition & 3 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Fn>`]()
- [Converting C++ objects to `Box<dyn Trait>`]()
- [`Opaque C++ types`](./call_cpp_from_rust/opaque.md)
- [Safety](./safety.md)
- [How it compares to other tools](./how_it_compares.md)
Expand Down
47 changes: 46 additions & 1 deletion book/src/call_cpp_from_rust/opaque.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Trait>`. To do that, you need to:

- Create a opaque borrowed (or owned for `Box<dyn Trait>`) 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::<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));
```

## 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
Expand All @@ -54,4 +98,5 @@ to represent the whole C++ object. Some examples (assume `RustType` is a newtype
- `std::mem::alignof::<RustType>()` is 1, not the align of `CppType`
- `std::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.
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.
44 changes: 42 additions & 2 deletions book/src/call_cpp_from_rust/rust_impl.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,3 +35,41 @@ rust::Impl<TagList>::get_value_by_key(rust::Ref<TagList> 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<usize, Output = crate::Node> 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<Node>
rust::Impl<WayNodeList, rust::std::ops::Index<size_t, Node>>::index(
rust::Ref<WayNodeList> self, size_t i) {
// Your code here
}
```
36 changes: 0 additions & 36 deletions book/src/call_cpp_from_rust/rust_impl_trait.md

This file was deleted.

5 changes: 3 additions & 2 deletions book/src/how_it_compares.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ for your C++ type, and then convert that type into a `Box<dyn Trait>` 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<u8>`, or `String` if the performance cost is acceptable.
- Write a trait for functionalities you need from it and convert the string to `Box<dyn Trait>`
- Write a trait for functionalities you need from it and convert the string to `Box<dyn Trait>`.
- Write an opaque type `CxxString` and implement the functionalities you need in `impl`.
- `CxxVector<uintX_t>`:
- Similar to `CxxString`
- `CxxVector<opaque_type>`:
- Copy it into a Rust `Vec<Box<dyn Trait>>` 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

Expand Down
25 changes: 23 additions & 2 deletions book/src/philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -24,6 +36,14 @@ tools between Rust and higher level languages, Zngur is not allowed to do deep c

### `Result<T, E>` is not automatically converted to exception

`Result<T, E>` 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<T, E>` is added to the C++23. In order to not losing
this Rust benefit, `Result<T, E>` 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<T, E>`, 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
Expand Down Expand Up @@ -62,7 +82,8 @@ if (reserve_capacity) {
```

If `Vec<int32_t> 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`

Expand Down
1 change: 1 addition & 0 deletions zngur-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions zngur-def/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions zngur-generator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions zngur-parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions zngur/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 15 additions & 0 deletions zngur/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<PathBuf>,
Expand Down

0 comments on commit 41082c7

Please sign in to comment.