From 5d85502ef3e293f7eab330f335b2503a4438de34 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 00:30:12 -0700 Subject: [PATCH 1/9] Add more info to intro pages --- src/intro.md | 26 ++++++++++++++++++++++---- src/user.md | 7 ++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/intro.md b/src/intro.md index 8d04fc0..cc0569a 100644 --- a/src/intro.md +++ b/src/intro.md @@ -12,9 +12,24 @@ You can read the full design doc [here](https://github.com/rust-diplomat/diploma Diplomat does not do cross-crate global analysis, it restricts its view to specially tagged modules, and only generates bindings based on information found in those modules. This means that changing some struct in some dependency will not magically affect your generated C++/JS/etc APIs; all such change can only come from deliberate change to these tagged modules. This also means that Diplomat can cleanly define a subset of Rust used for declaring the exported API without impacting the flavor of Rust used in dependencies. One can imagine `#[diplomat::bridge]` blocks to almost be a DSL for bridging between your Rust APIs and a more general API shape that can be translated cleanly across languages. -Diplomat is designed such that it should not be a large amount of effort to write new language targets for Diplomat. +Diplomat is designed such that it should not be a large amount of effort to [write new language targets for Diplomat](developer.html). +## Backends supported + + +Diplomat currently supports the following backends: + + - C + - C++ + - JavaScript/TypeScript (using WASM) + - Dart + - Kotlin (using JNA) + +There is work in progress for a [Java backend] (using Panama), as well as a [demo-autogenerating backend]. We used to have a .NET backend but it was removed in a refactor, it may get added again. + +We're happy to fix bugs or add configurability in the current backends if their produced output does not match what you need in your language. Details on how to write new backends is documented [later in this book](developer.html): you can do so as a third party library depending on `diplomat_core`, but we are also happy to accept these into Diplomat with the understanding that we'll only do minimal work to keep them working over time. + ## Setup To install the `diplomat` CLI tool, run @@ -23,15 +38,18 @@ To install the `diplomat` CLI tool, run $ cargo install diplomat-tool ``` +Let's say this installs `diplomat-tool 0.8.0` You can then add `diplomat` as a dependency to your project like so: ```toml -diplomat = "0.5.0" -diplomat-runtime = "0.5.0" +diplomat = "0.8.0" +diplomat-runtime = "0.8.0" ``` It is recommended to create a separate crate for the FFI interface. Diplomat will only read the contents of specially tagged modules so it is possible to mix Diplomat code with normal Rust code, but it is prefereable to minimize this since proc macros can make debugging hard. - [Diplomat]: https://github.com/rust-diplomat/diplomat \ No newline at end of file + [Diplomat]: https://github.com/rust-diplomat/diplomat + [Java backend]: https://github.com/rust-diplomat/diplomat/issues/144 + [demo-autogenerating backend]: https://github.com/rust-diplomat/diplomat/issues/604 \ No newline at end of file diff --git a/src/user.md b/src/user.md index 6274341..a1c1abe 100644 --- a/src/user.md +++ b/src/user.md @@ -8,12 +8,13 @@ To install the `diplomat` CLI tool, run $ cargo install diplomat-tool ``` +Let's say this installs `diplomat-tool 0.8.0` You can then add `diplomat` as a dependency to your project like so: ```toml -diplomat = "0.5.0" -diplomat-runtime = "0.5.0" +diplomat = "0.8.0" +diplomat-runtime = "0.8.0" ``` -It is recommended to create a separate crate for the FFI interface. Diplomat will only read the contents of specially tagged modules so it is possible to mix Diplomat code with normal Rust code, but it is prefereable to minimize this since proc macros can make debugging hard. \ No newline at end of file +It is recommended to create a separate crate for the FFI interface. Diplomat will only read the contents of specially tagged modules so it is possible to mix Diplomat code with normal Rust code, but it is prefereable to minimize this since proc macros can make debugging hard. From a1a30c41bd9d507a425d7a6d0f11c4f90489cbb6 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 01:16:58 -0700 Subject: [PATCH 2/9] More listing of types --- src/structs.md | 6 +++--- src/types.md | 27 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/structs.md b/src/structs.md index 0b43229..2c2684d 100644 --- a/src/structs.md +++ b/src/structs.md @@ -49,7 +49,7 @@ mod ffi { } ``` -Enums exposed via Diplomat must be simple C-like enums. Structs may only contain fields which are [allowed](./types.md). +Enums exposed via Diplomat must be simple C-like enums. They can have explicit discriminants. Structs may only contain fields which are themselves [allowed types](./types.md). In C++ the structs are translated to simple structs and the enums become simple enum classes. In JS the structs become objects with fields, and the enums are exposed as strings that get converted at the boundary. @@ -72,11 +72,11 @@ mod ffi { } ``` -In case the enum is `#[non_exhaustive]`, you may need to supply a `needs_wildcard` argument, like so: `#[diplomat::enum_convert(my_library::SpeedSetting)]`. +In case the enum is `#[non_exhaustive]`, you may need to supply a `needs_wildcard` argument, like so: `#[diplomat::enum_convert(my_library::SpeedSetting, needs_wildcard)]`. # Structs containing boxes -By default, structs cannot contain output-only types like `Box`. This can be opted in to by using `#[diplomat::out]` +By default, structs cannot contain output-only types like `Box`. This can be opted in to by using `#[diplomat::out]`, which will have the additional effect of making the struct an output-only type. ```rust diff --git a/src/types.md b/src/types.md index 86bac71..36bd1de 100644 --- a/src/types.md +++ b/src/types.md @@ -1,19 +1,30 @@ # Types -Diplomat only supports a small set of types that can be passed over FFI. These are: +Diplomat only supports a small set of types that can be passed over FFI. + - Builtins: - - All integers, as well as `bool` and `char` - - `&[T]` where `T` is an integer, `bool`, or `char` - - `&str` (string slices) - - [`DiplomatWriteable`](./writeable.md) for returning strings - - [`Result`](./result.md) in return values - - [`Option`](./option.md) of opaque types + - All integers + - `bool` + - `char` + - `DiplomatChar` (`u32`), which is treated as `char`-equivalent on the backend language, but need not be a valid Unicode code point for the Rust code to be sound. + - Slices, `&[T]` where `T` is one of: + - An integer type + - `bool` + - `char` + - `DiplomatByte` (`u8`): The same as `u8` except in languages where "byte buffer" and "list of integers" are different types + - String slices: + - `&str`: A validated, UTF-8 string. Will be converted/validated by the target language bindings if necessary. + - `&DiplomatStr`: An unvalidated string expected to be UTF-8. + - `&DiplomatStr16`: An unvalidated string expected to be UTF-16. + - [`DiplomatWriteable`](./writeable.md) for returning strings. This needs to be the last parameter of the method. + - [`Option<&T>` ,`Option>`](./option.md) of opaque types + - [`Result` and `Option`](./result.md) in return values - `()` as a `Result` `Ok`/`Error` type, or as a return value - Custom types - Custom [opaque types](./opaque.md) (passed as references or via `Box`) - Custom [structs and C-like enums](./structs.md) -More types can be supported in the future (We have issues for [iterators](https://github.com/rust-diplomat/diplomat/issues/251) and [callbacks](https://github.com/rust-diplomat/diplomat/issues/146)) +More types can be supported in the future (We have issues for [callbacks](https://github.com/rust-diplomat/diplomat/issues/146) and [full option types](https://github.com/rust-diplomat/diplomat/issues/246)) The _main_ distinction to keep track of is between "opaque types" and "structs": opaque types are for when you want to wrap a Rust object that has its own semantics, whereas "structs" are for when you want to transparently pass around multiple values at once (usually when you want to make an options struct as an argument, or return multiple values at once). From 183d4bc80e0945afc7e6a4ecfd1c5a173fa211a2 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 01:25:02 -0700 Subject: [PATCH 3/9] Option --- src/opaque.md | 10 ++++------ src/option.md | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/opaque.md b/src/opaque.md index 4c7f9f2..197323f 100644 --- a/src/opaque.md +++ b/src/opaque.md @@ -33,17 +33,17 @@ struct MyCollection { } impl MyCollection { - fn new(name: String) -> Self { + pub fn new(name: String) -> Self { Self { name, items: vec![] } } - fn push(&mut self, s: String) { + pub fn push(&mut self, s: String) { self.items.push(s) } - fn dump(&self) { + pub fn dump(&self) { println!("Collection {} with items {:?}", self.name, self.items); } } @@ -84,13 +84,11 @@ For example, the generated C++ looks something like ```cpp class MyCollection { public: - static MyCollection create(const std::string_view s); + static std::unique_ptr create(const std::string_view s); void push(const std::string_view s); void dump(); // snip private: - // just a pointer with a custom destructor - std::unique_ptr inner; }; ``` diff --git a/src/option.md b/src/option.md index 667b83b..cda2135 100644 --- a/src/option.md +++ b/src/option.md @@ -2,7 +2,7 @@ Option types in Diplomat are relatively straightforward, you simply use `Option` and it turns into the idiomatic equivalent over FFI. -`Option` currently only works when wrapping reference types (`Box` and `&OpaqueType`). +`Option` currently only works when wrapping reference types (`Box` and `&OpaqueType`), or in return type position: ```rust #[diplomat::bridge] @@ -12,11 +12,21 @@ mod ffi { pub struct Thingy; impl Thingy { - fn maybe_create() -> Option> { + pub fn maybe_create() -> Option> { Some(Box::new(Thingy)) } + + // works in return position, but not elsewhere + pub fn make_option() -> Option { + Some(1) + } } } ``` -In C++ this will return a `std::option`, and in JS it will return a potentially-null object. \ No newline at end of file +In C++ `maybe_create` will return a `std::optional>`, and in JS it will return a potentially-null object. + +`make_option` will have similar behavior, returning `std::optional` and an integer-or-null in JS. + + +In the future, `Option` will be supported for most types `T` ([#246](https://github.com/rust-diplomat/diplomat/issues/246)). \ No newline at end of file From c88ac46909c925da2baec4c2a58dab567361e67d Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 01:25:52 -0700 Subject: [PATCH 4/9] results --- src/result.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/result.md b/src/result.md index 32888cd..d5a5e7b 100644 --- a/src/result.md +++ b/src/result.md @@ -22,7 +22,7 @@ mod ffi { On the C++ side, this will generate a method on `Thingy` with the signature ```cpp - static diplomat::result try_create(const std::string_view string); + static diplomat::result, std::monostate> try_create(const std::string_view string); ``` `diplomat::result` is a type that can be found in the generated [`diplomat_runtime.hpp`](https://github.com/rust-diplomat/diplomat/blob/main/tool/src/cpp/runtime.hpp) file. The most basic APIs are `.is_ok()` and `.is_err()`, returning `bool`s, and `.ok()` and `.err()` returning `std::option`s. There are further APIs for constructing and manipulating these that can be found in the header file. From 5af4c5c26213f8634125242c98598557c6dd86be Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 01:29:38 -0700 Subject: [PATCH 5/9] writeables --- src/SUMMARY.md | 2 +- src/writeable.md | 24 ++---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 51a466c..d17aa8f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -8,7 +8,7 @@ - [Structs and enums](./structs.md) - [Options](./option.md) - [Results](./result.md) - - [Writeable](./writeable.md) + - [Returning Strings: Writeable](./writeable.md) - [Documentation](docs.md) - [Lifetimes](lifetimes.md) - [Backend developer guide](developer.md) diff --git a/src/writeable.md b/src/writeable.md index 1cbb937..4de9f9b 100644 --- a/src/writeable.md +++ b/src/writeable.md @@ -1,4 +1,4 @@ -# Writeables +# Returning strings: Writeables Most languages have their own type to handle strings. To avoid unnecessary allocations, Diplomat supports [`DiplomatWriteable`](https://docs.rs/diplomat-runtime/0.2.0/diplomat_runtime/struct.DiplomatWriteable.html), a type with a `Write` implementation which can be used to write to appropriate string types on the other side. @@ -28,28 +28,8 @@ mod ffi { On the JS side these will get converted to APIs that return strings (`maybe_get_string` will potentially throw in the case of an error, as is usual with `DiplomatResult`) -In C++ multiple APIs are generated: +In C++ these become APIs that return `std::string` and `diplomat::result` respectively. -```cpp - std::string debug_output(); - diplomat::result maybe_get_string(); -// and - template void debug_output_to_writeable(W& writeable); - template diplomat::result maybe_get_string_to_writeable(W& writeable); -``` Essentially, versions of the API returning `std::string` are generated, where the `write!()` operation will end up writing _directly to the `std::string`_ with no additional intermediate Rust `String` allocations. -## WriteableTrait - -The template versions work on any type that is hooked into `WriteableTrait`, allowing . Types can be hooked into `WriteableTrait` as follows: - -```cpp -template<> struct WriteableTrait { - static inline capi::DiplomatWriteable Construct(MyStringType& t) { - // ... - } -} -``` - -This requires constructing a [`DiplomatWriteable`](https://docs.rs/diplomat-runtime/0.2.0/diplomat_runtime/struct.DiplomatWriteable.html) from the custom string type, which is documented in more detail [in the source](https://github.com/rust-diplomat/diplomat/blob/38cffa9bc2ef21d0aba89ed7d76236de4153248a/runtime/src/writeable.rs#L6-L62). Essentially, it involves constructing an ad-hoc virtual dispatch object for the type. From 0cbacbbbee38ac0147184d4c1d71f006d645b030 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 01:31:00 -0700 Subject: [PATCH 6/9] lifetimes --- src/lifetimes.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lifetimes.md b/src/lifetimes.md index 9fbea38..fe2c17d 100644 --- a/src/lifetimes.md +++ b/src/lifetimes.md @@ -31,11 +31,8 @@ mod ffi { } impl<'a> MyFancyIterator<'a> { - fn next(&mut self) -> u32 { - // We don't support Option of primitives in Diplomat currently - // Just default to 0. We could use a struct to represent this instead - // but it's not relevant to the example - self.0.next().copied().unwrap_or(0) + fn next(&mut self) -> Option { + self.0.next().copied() } } } From bbf1f96cfd47a3fb70e01bee2d510dc7fcae70f8 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 02:31:51 -0700 Subject: [PATCH 7/9] Add abi_rename --- src/SUMMARY.md | 1 + src/abi.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/abi.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d17aa8f..9e32971 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -11,4 +11,5 @@ - [Returning Strings: Writeable](./writeable.md) - [Documentation](docs.md) - [Lifetimes](lifetimes.md) + - [ABI naming/renaming](abi.md) - [Backend developer guide](developer.md) diff --git a/src/abi.md b/src/abi.md new file mode 100644 index 0000000..7f27677 --- /dev/null +++ b/src/abi.md @@ -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. \ No newline at end of file From fbbf1e5ab3cc6985543530a635ced2f9130776e0 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 02:59:59 -0700 Subject: [PATCH 8/9] Attributes --- src/SUMMARY.md | 10 +++++++ src/attrs.md | 38 ++++++++++++++++++++++++ src/attrs/accesors.md | 1 + src/attrs/accessors.md | 62 +++++++++++++++++++++++++++++++++++++++ src/attrs/comparators.md | 28 ++++++++++++++++++ src/attrs/constructors.md | 27 +++++++++++++++++ src/attrs/disable.md | 28 ++++++++++++++++++ src/attrs/indexing.md | 23 +++++++++++++++ src/attrs/iterators.md | 50 +++++++++++++++++++++++++++++++ src/attrs/namespace.md | 25 ++++++++++++++++ src/attrs/rename.md | 25 ++++++++++++++++ src/attrs/stringifiers.md | 25 ++++++++++++++++ 12 files changed, 342 insertions(+) create mode 100644 src/attrs.md create mode 100644 src/attrs/accesors.md create mode 100644 src/attrs/accessors.md create mode 100644 src/attrs/comparators.md create mode 100644 src/attrs/constructors.md create mode 100644 src/attrs/disable.md create mode 100644 src/attrs/indexing.md create mode 100644 src/attrs/iterators.md create mode 100644 src/attrs/namespace.md create mode 100644 src/attrs/rename.md create mode 100644 src/attrs/stringifiers.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9e32971..8deaf42 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -12,4 +12,14 @@ - [Documentation](docs.md) - [Lifetimes](lifetimes.md) - [ABI naming/renaming](abi.md) + - [Customizing via attributes](attrs.md) + - [Disabling APIs](attrs/disable.md) + - [Renaming APIs](attrs/rename.md) + - [Namespacing](attrs/namespace.md) + - [Constructors](attrs/constructors.md) + - [Iterators and iterables](attrs/iterators.md) + - [Getters and setters](attrs/accessors.md) + - [Indexing](attrs/indexing.md) + - [Comparators](attrs/comparators.md) + - [Stringifiers](attrs/stringifiers.md) - [Backend developer guide](developer.md) diff --git a/src/attrs.md b/src/attrs.md new file mode 100644 index 0000000..d9a2905 --- /dev/null +++ b/src/attrs.md @@ -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 \ No newline at end of file diff --git a/src/attrs/accesors.md b/src/attrs/accesors.md new file mode 100644 index 0000000..91c1df9 --- /dev/null +++ b/src/attrs/accesors.md @@ -0,0 +1 @@ +# Getters and setters diff --git a/src/attrs/accessors.md b/src/attrs/accessors.md new file mode 100644 index 0000000..9eea824 --- /dev/null +++ b/src/attrs/accessors.md @@ -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. diff --git a/src/attrs/comparators.md b/src/attrs/comparators.md new file mode 100644 index 0000000..f049225 --- /dev/null +++ b/src/attrs/comparators.md @@ -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`. + +We currently do not but Diplomat would like to also support the ability to _just_ overload `==`. + diff --git a/src/attrs/constructors.md b/src/attrs/constructors.md new file mode 100644 index 0000000..a45e02a --- /dev/null +++ b/src/attrs/constructors.md @@ -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 { + 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`. \ No newline at end of file diff --git a/src/attrs/disable.md b/src/attrs/disable.md new file mode 100644 index 0000000..9cefa87 --- /dev/null +++ b/src/attrs/disable.md @@ -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. \ No newline at end of file diff --git a/src/attrs/indexing.md b/src/attrs/indexing.md new file mode 100644 index 0000000..14c97d7 --- /dev/null +++ b/src/attrs/indexing.md @@ -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`. + +```rust +#[diplomat::bridge] +mod ffi { + #[diplomat::opaque] + struct Foo(Vec); + + impl Foo { + #[diplomat::attr(auto, indexer)] + pub fn get(&self, idx: usize) -> Option { + self.0.get(idx).copied() + } + } +} +``` + + diff --git a/src/attrs/iterators.md b/src/attrs/iterators.md new file mode 100644 index 0000000..1433a57 --- /dev/null +++ b/src/attrs/iterators.md @@ -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 { + 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); + + impl MyVector { + #[diplomat::attr(auto, iterable)] + pub fn iter<'a> (&'a self) -> Box> { + Box::new(MyIterator(self.0.iter())) + } + } +} +``` \ No newline at end of file diff --git a/src/attrs/namespace.md b/src/attrs/namespace.md new file mode 100644 index 0000000..c7590c7 --- /dev/null +++ b/src/attrs/namespace.md @@ -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). \ No newline at end of file diff --git a/src/attrs/rename.md b/src/attrs/rename.md new file mode 100644 index 0000000..f3067ef --- /dev/null +++ b/src/attrs/rename.md @@ -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. diff --git a/src/attrs/stringifiers.md b/src/attrs/stringifiers.md new file mode 100644 index 0000000..132f954 --- /dev/null +++ b/src/attrs/stringifiers.md @@ -0,0 +1,25 @@ +# Stringifiers + +(Supported by `dart`, queried with `supports = stringifiers`. Intended to be supported in `js` and `kotlin`) + +Some languages have a designated way to provide a method for converting a type to a string. + +The `stringifier` attribute can be applied to such a method: + +```rust +#[diplomat::bridge] +mod ffi { + #[diplomat::opaque] + struct Foo; + + impl Foo { + #[diplomat::attr(auto, stringifier)] + pub fn dump(&self, out: &mut DiplomatWrite) { + ... + } + } +} +``` + + +In Dart, this will generate a `toString()` method. From 4ab262e89f45b253d8a25b2cb553a57362d3b55e Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 30 Jul 2024 04:55:53 -0700 Subject: [PATCH 9/9] safety --- src/SUMMARY.md | 1 + src/safety.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/safety.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8deaf42..88d3a8f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -22,4 +22,5 @@ - [Indexing](attrs/indexing.md) - [Comparators](attrs/comparators.md) - [Stringifiers](attrs/stringifiers.md) + - [Notes on Diplomat and safety](safety.md) - [Backend developer guide](developer.md) diff --git a/src/safety.md b/src/safety.md new file mode 100644 index 0000000..96a25ab --- /dev/null +++ b/src/safety.md @@ -0,0 +1,67 @@ +# Notes on Diplomat and safety + +Overall, Diplomat is intended to provide safe FFI. However, there are some caveats around the current state of Diplomat's safety. + + +## The safety of the bridge crate when called from Rust + +Diplomat's `extern "C"` functions are generated in a "bridge crate" that contains "bridge modules" tagged with `#[diplomat::bridge]`. This generated code is not designed with Rust as a primary client, rather, it is designed to be used from C. + +As such, it may be possible to trigger unsoundness by directly calling these `extern "C"` APIs from Rust. Work shoring this up is welcome. + + +## Aliasing safety + +Primary issue: [#225](https://github.com/rust-diplomat/diplomat/issues/225) + +In Rust, it is undefined behavior to have any additional references (`&` or `&mut`) to some data whilst there is an active `&mut T` reference to it. + +Diplomat currently lets you do the following: + +```rust +#[diplomat::bridge] +mod ffi { + + #[diplomat::opaque] + struct Foo(u8); + + #[diplomat::opaque] + struct Bar(Foo); + + + impl Bar { + pub fn change_foo_number(&mut self, num: u8) { + self.0.0 = num; + } + pub fn get_foo(&self) -> &Foo { + &self.0 + } + } +} + +``` + +Calling `change_foo_number()` while a reference from `get_foo()` is active could be UB, or cause thread safety issues in multithreaded code. + +Diplomat has [plans](https://github.com/rust-diplomat/diplomat/issues/225) on how to fix this, but until this is fixed, it can be avoided by doing the following: + + - Limit the number of mutable methods you expose (use `RefCell` where needed) + - If you wish to expose a mutable method, ensure that: + - The type cannot ever be obtained as an `&T` or `&mut T` from some other type + - The type does not have any methods that produce an `&U` or `&mut U`, where `U` is an opaque type or slice that borrows from things that the method intends to mutate. + + +As long as you're careful, this should not be an issue. ICU4X tends to not need much in the way of mutation and is so far safe from this. + +## Thread safety + +Primary issue: [#533](https://github.com/rust-diplomat/diplomat/issues/533) + + +So far ICU4X has mostly been targeting languages where thread safety is locked down by thread isolation (Dart, JS), or where thread safety is a matter of documentation convention (C++), so it has not yet needed to think deeply about this. However, other users of Diplomat may. + + +Specific backends may choose their own route as to how they apply and enforce thread safety. Diplomat may over time add some annotation system to help with this. Limiting mutation and using `RwLock`/`Mutex` can help here. + + +