-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from rust-diplomat/updated-docs
A whole bunch of doc improvements to the book
- Loading branch information
Showing
23 changed files
with
533 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# ABI naming/renaming | ||
|
||
As previously mentioned, the `#[diplomat::bridge]` macro from the `diplomat` crate will generate `extern "C"` functions for all methods expected to be exposed over FFI. | ||
|
||
_By default_, the naming scheme for these types is `TypeName_method()`. For example, `Foo::bar()` becomes `Foo_bar()`. | ||
|
||
However, these names can be changed, both at an individual and bulk level, using the `#[diplomat::abi_rename = "..."]` attribute. | ||
|
||
One use case for this is maintaining ABI stability. Let's say you wished to change the signature or even behavior of `Foo::bar()` to go from returning a `u8` to returning a `u16`, and were okay with requiring new bindings for the new behavior, but wanted to ensure that old headers/bindings would still work against new Rust code without any change. You could do that by | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
|
||
#[diplomat::opaque] | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[diplomat::abi_rename = "Foo_bar"] | ||
pub fn old_bar(&self) -> u8 {} | ||
|
||
#[diplomat::abi_rename = "Foo_bar2"] | ||
pub fn bar(&self) -> u16 {} | ||
} | ||
} | ||
``` | ||
|
||
Here, `old_bar()` is still exposed over FFI as `Foo_bar()`, whereas the new `bar()` method is exposed as `Foo_bar2()`. | ||
|
||
|
||
However, from the point of view of higher-level bindings generated over this (e.g. in C++ or JS), `Foo` now has a `bar()` method with a new signature, and an `old_bar()` method with the signature `bar()` used to have. | ||
|
||
|
||
The attribute can be applied directly on methods, but it can also be applied on impl blocks, types, and even entire bridge modules. It supports using replacement patterns, so it can be used for namespacing the generated FFI functions: | ||
|
||
|
||
```rust | ||
#[diplomat::bridge] | ||
#[diplomat::abi_rename = "mylibrary_{}"] | ||
mod ffi { | ||
|
||
#[diplomat::opaque] | ||
struct Foo; | ||
|
||
impl Foo { | ||
pub fn bar() -> u8 {} | ||
} | ||
} | ||
``` | ||
|
||
Here, instead of the function being generated as `Foo_bar()`, it will be generated as `mylibrary_Foo_bar()` which is a more unique symbol, less likely to cause trouble for the linker. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Customizing via backend attributes | ||
|
||
Diplomat supports a number of "backend attributes" that allow one to customize the backend-side code generation of a particular API. This allows for more expressivity than that afforded by Diplomat's relatively constrained type system, including things like the ability to rename types, namespace types, or mark types as iterators. | ||
|
||
|
||
## Configurability | ||
|
||
All backend attributes are applied via the `#[diplomat::attr(...)]` directive. As its first parameter, it requires a configuration specification, which can be: | ||
|
||
- `*`, meaning "all backends" | ||
- `auto`, explained below | ||
- A backend/language name: (`c`, `cpp`, `js`, `dart`, `kotlin`, `demo`, ...). Note that a backend may accept multiple names, for example the `demo` backend both accepts attributes marked for the `js` and `demo` backends. | ||
- A `supports` query, like `supports = constructors`. A full list can be found on the [`BackendAttrSupport`] type, but furthermore the pages on individual attributes will specifically list configuration options relevant to that attribute. | ||
- `not(...)` containing a configuration specification | ||
- `any(...)` containing one or more comma separated configuration specifications | ||
- `all(...)` containing one or more comma separated configuration specifications | ||
|
||
|
||
So, for example, to rename a type for everyone, one may use something like `#[diplomat::attr(*, rename = "FancyNewName")]` on the type. However if the rename is only intended for a select set of backends, one may do something like `#[diplomat::attr(any(js, cpp), rename = "FancyNewName")]`. | ||
|
||
|
||
Similarly, if one wishes to only expose a type to backends that support iterators, one may do something like `#[diplomat::attr(not(supports = iterators), disable)]`. | ||
|
||
|
||
A lot of attributes (basically all of them except for `rename` and `disable`) represent features that need not be present in all backends. For example, not every backend wishes to, or can, support iteration, or namespacing, or accessors. By default when an unsupported attribute is passed to a backend, Diplomat will error as a lint. However, this has the unfortunate effect of sprinkling the code with a lot of stuff that looks like `#[diplomat::attr(supports = iterators, iterator)]`. | ||
|
||
To aid in that, `auto` can be used instead. `auto` is roughly equivalent to `supports = whichever attribute this is attempting to apply`. | ||
|
||
|
||
|
||
## When to configure | ||
|
||
Besides for reasons of differing backend support, Diplomat's attribute configuration is useful for providing a more idiomatic, tailored experience for individual languages. For example, in some languages (like C++) adding a field to a struct accepted as a parameter in a function would be a breaking change, but in languages like JS that change can be made without any breakage if the new field is nullable. In such a case, the appropriate mixing of language-specific renames can lead to a split v1/v2 API in C++ whilst JS gets a smoother experience with no need for versioning. | ||
|
||
|
||
|
||
|
||
[`BackendAttrSupport`]: https://docs.rs/diplomat_core/latest/diplomat_core/hir/struct.BackendAttrSupport.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Getters and setters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Getters and setters | ||
|
||
(Supported by `dart`, `js`, queried with `supports = accessors`) | ||
|
||
|
||
## Getters | ||
|
||
A method that returns a value (and takes no argument) can be marked as a getter. It may be fallible. | ||
|
||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct Foo { | ||
a: u8 | ||
}; | ||
|
||
impl Foo { | ||
#[diplomat::attr(auto, getter = "a")] | ||
pub fn get_a(&self) -> u8 { | ||
self.0 | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
If the name is not provided, the method name is used[^1]. | ||
|
||
In languages that support accessors, instead of there being a `Foo::get_a()` method, people can use field access syntax on `Foo`-typed objects. For example, in JS, `foo.a` will work. | ||
|
||
|
||
## Setters | ||
|
||
A method that accepts a value can be marked as a setter. `self` need not be mutable, and the method may be fallible. | ||
|
||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct Foo { | ||
a: u8 | ||
}; | ||
|
||
impl Foo { | ||
#[diplomat::attr(auto, setter = "a")] | ||
pub fn set_a(&mut self, a: u8) { | ||
self.0 = a | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
If the name is not provided, the method name is used. | ||
|
||
In languages that support accessors, instead of there being a `Foo::set_a()` method, people can use field access syntax on `Foo`-typed objects to set the value. For example, in JS, `foo.a = 1` will work. | ||
|
||
|
||
[^1]: A potential future improvement is to use the method name but strip common prefixes like `get_` and `set_`, which would allow a getter and setter for the same field to coexist without requiring them have explicit names. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Comparators | ||
|
||
(Supported by `dart`, queried with `supports = comparators`. Intended to be supported in `cpp` and `kotlin`) | ||
|
||
Some languages allow for overloading the `<`/`>`/`=`/etc operators, similar to Rust's `PartialOrd`. | ||
|
||
To expose this over Diplomat, use the `comparison` attribute on a method that takes another `Self` parameter: | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
#[derive(Ord, PartialOrd, Eq, PartialEq)] | ||
struct Foo(u8); | ||
|
||
impl Foo { | ||
#[diplomat::attr(auto, comparison)] | ||
pub fn compare(&self, other: &Foo) -> std::cmp::Ordering { | ||
self.cmp(other) | ||
} | ||
} | ||
} | ||
``` | ||
|
||
In Dart, this will provide an overloaded `==`, and `Foo` will implement `Comparable<Foo>`. | ||
|
||
We currently do not but Diplomat would like to also support the ability to _just_ overload `==`. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Constructors | ||
|
||
(Supported by `dart`, queried with `supports = constructors`. Intended to be supported in `kotlin`, `js`, and `cpp`) | ||
|
||
Methods that return `Self` can be marked as a constructor: | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[diplomat::attr(auto, constructor)] | ||
pub fn create() -> Box<Self> { | ||
Box::new(Foo) | ||
} | ||
} | ||
} | ||
``` | ||
|
||
|
||
and then instead of there being a regular `Foo::create()` method, `Foo` will now have a direct constructor `Foo()`. | ||
|
||
Constructors can additionally be given names using `#[diplomat::attr(auto, named_constructor = "make")]`, for languages that support named constructors (`supports = named_constructors`). | ||
|
||
Not all languages support fallible constructors, this can be queried with `supports = fallible_constructors`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Disabling APIs | ||
|
||
(Supported by all backends) | ||
|
||
Any type or method can be "disabled" by applying the `disable` attribute: | ||
|
||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
#[diplomat::attr(cpp, disable)] | ||
struct Foo; | ||
|
||
|
||
impl Foo { | ||
#[diplomat::attr(js, disable)] | ||
pub fn bar() {} | ||
pub fn baz() {} | ||
} | ||
} | ||
``` | ||
|
||
|
||
Here, the class `Foo` will not show up in the C++ backend. It will in the JS backend, however it will not have the function `bar()`. | ||
|
||
|
||
Currently enum variants cannot be disabled, however this is technically feasible for input-only enums and could be added if people request. It is also not possible to disable struct fields. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Indexing | ||
|
||
(Supported by `dart`, queried with `supports = indexing`. Intended to be supported in `cpp`) | ||
|
||
|
||
`[]` can be overloaded in languages that support it by applying an `indexer` attribute to a method that takes an integer argument and returns an `Option<u8>`. | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct Foo(Vec<u8>); | ||
|
||
impl Foo { | ||
#[diplomat::attr(auto, indexer)] | ||
pub fn get(&self, idx: usize) -> Option<u8> { | ||
self.0.get(idx).copied() | ||
} | ||
} | ||
} | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Iterators and iterables | ||
|
||
(Supported by `js`, `dart`, `kotlin`, queried with `supports = iterators` and `supports = iterables`) | ||
|
||
Some languages support a first-class notion of "iterator", a type that can be looped over in a `for` loop, stepping through its values typically until they are exhausted. | ||
|
||
Furthermore, some languages support a first-class notion of an "iterable": types like collections that can be asked to _produce_ an iterator over their values which can be stepped through. Typically `for` loops will also accept iterables, and looping through an iterable involves producing and then looping through the associated iterator. | ||
|
||
The distinction here is: iterators are mutated by iteration and often one-time use, iterables are not mutated and can be iterated over multiple times. Put in Rust terms, `Iterator` types are iterators, and `IntoIterator` types are iterables. | ||
|
||
Diplomat supports marking types as such by using `iterator` and `iterable` on a specific _method_ of the type. | ||
|
||
## Marking a type as an iterator | ||
|
||
To mark a type as an iterator, it should have a signature of `fn next(&mut self) -> Option<...>`. The method name may be anything, and it may take `self` by immutable reference instead, if needed (this is useful for situations where aliasing safety is needed, see [#225](https://github.com/rust-diplomat/diplomat/issues/225) ). | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct MyIterator<'a>(std::slice::Iter<'a, u8>); | ||
|
||
impl<'a> MyIterator<'a> { | ||
#[diplomat::attr(auto, iterator)] | ||
pub fn next(&mut self) -> Option<u8> { | ||
self.0.next().copied() | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Marking a type as an iterable | ||
|
||
|
||
Marking a type as an iterable requires annotating it with `iterable`, and it just needs to be a method that takes `self` and returns a type that has a method marked as `iterator`. | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct MyVector(Vec<u8>); | ||
|
||
impl MyVector { | ||
#[diplomat::attr(auto, iterable)] | ||
pub fn iter<'a> (&'a self) -> Box<MyIterator<'a>> { | ||
Box::new(MyIterator(self.0.iter())) | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Namespacing | ||
|
||
(Supported by `cpp`, queried with `supports = namespacing`) | ||
|
||
This attribute can be applied on types and on bridge modules (applying to all types in the module). | ||
It allows code to be organized under one or more namespaces. | ||
|
||
```rust | ||
#[diplomat::bridge] | ||
#[diplomat::attr(auto, namespace = "mylib")] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
struct Foo; | ||
|
||
#[diplomat::opaque] | ||
struct Bar; | ||
} | ||
``` | ||
|
||
Here, in C++ `Foo` and `Bar` will both be available as `mylib::Foo` and `mylib::Bar`. | ||
|
||
|
||
Nested namespaces are technically supported using things like `mylib::mymodule`, however they're not tested and support is brittle, so use at your own risk ([#591](https://github.com/rust-diplomat/diplomat/issues/591)). | ||
|
||
There is some discussion over separating the concept of a namespace and a library in [#589](https://github.com/rust-diplomat/diplomat/issues/589). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Renaming APIs | ||
|
||
(Supported by all backends) | ||
|
||
Any type, method, field, or enum variant can be renamed with the `rename` attribute. | ||
|
||
|
||
```rust | ||
#[diplomat::bridge] | ||
mod ffi { | ||
#[diplomat::opaque] | ||
#[diplomat::attr(cpp, rename = "Foo2")] | ||
struct Foo; | ||
|
||
impl Foo { | ||
#[diplomat::attr(js, rename = "barbar")] | ||
pub fn bar() {} | ||
pub fn baz() {} | ||
} | ||
} | ||
``` | ||
|
||
Here, C++ will see a class `Foo2` instead of `Foo`, and in JS the method `bar()` will be renamed to `barbar()`. | ||
|
||
Note that some backends apply some of their own renames as well, which will be applied on top of the attribute rename. For example, the JS and Dart backends both turn `snake_case` into `camelCase` for better idiomaticity. Renaming to `bar_bar` would then produce `barBar()` in these backends. |
Oops, something went wrong.