Skip to content

Commit

Permalink
Cleanup the docs, splitting UDL or proc-macro details from the genera…
Browse files Browse the repository at this point in the history
…l concepts. (#2347)

For example, types/interfaces.md is generic information about objects/interfaces
which applied to both proc_macros and UDL. udl/interfaces.md has info relevant
only to UDL and proc_macro/interfaces.md has info relevant only to macros.
The general page (types/interfaces.md) has numerous links to the specialized pages.

There's a lot more we can do here, but this seems like a nice improvement.
  • Loading branch information
mhammond authored Dec 12, 2024
1 parent db5ff26 commit 4cbb8f1
Show file tree
Hide file tree
Showing 37 changed files with 1,254 additions and 1,056 deletions.
2 changes: 2 additions & 0 deletions docs/manual/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ works.
Deploying using mkdocs will publish into the root of the site, **damaging
the versioning system we have in place.**

* We do however use `mkdocs` to locally test/preview the docs - see `/mkdocs.yml`.

* We use a [`mike`](https://github.com/jimporter/mike) to manage
the `mkdocs` build process and the deployment - it deploys to a versioned
(eg, `./0.27`) directory and manages aliases (eg, `latest`) on the site.
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/src/Upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ uniffi::custom_type!(NewCustomType, BridgeType, {
```

The `custom_type!` macro is more flexible than the old system - eg, the closures can be omitted in many cases where `From` and `Into` exist.
See the [Custom Types](./udl/custom_types.md) for details.
See the [Custom Types](./types/custom_types.md) for details.
7 changes: 7 additions & 0 deletions docs/manual/src/describing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Describing the interface

UniFFI allows you to define your object model using both [Procedural Macros](./proc_macro/index.md)
and via stand-alone [UDL files](./udl/index.md).

Each library can choose to use either or both of these techniques.

16 changes: 8 additions & 8 deletions docs/manual/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ This process can take place either during the build process or be manually initi

## Supported languages

- Kotlin
- Swift
- Python
- Ruby
UniFFI comes with full support for Kotlin, Swift and Python; unless specified otherwise, you can expect all features in
this manual will work for these languages.

## Third-party foreign language bindings
We also have partial legacy support for Ruby; the UniFFI team keeps the existing Ruby support working but tends to not
add new features to that language. It seems possible that Ruby support will be split into its own crate at some point, but
in the meantime we welcome improvements and contributions to Ruby.

* [Kotlin Multiplatform](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings)
* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
There are also many 3rd party bindings - please see our [README](https://github.com/mozilla/uniffi-rs/blob/main/README.md) for references.
These languages may require older versions of UniFFI and may have partial or non-existant support for some features; see the
documentation for those bindings for details.
4 changes: 2 additions & 2 deletions docs/manual/src/kotlin/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ The generated Kotlin modules can be configured using a `uniffi.toml` configurati
| `package_name` | `uniffi` | The Kotlin package name - ie, the value used in the `package` statement at the top of generated files. |
| `cdylib_name` | `uniffi_{namespace}`[^1] | The name of the compiled Rust library containing the FFI implementation (not needed when using `generate --library`). |
| `generate_immutable_records` | `false` | Whether to generate records with immutable fields (`val` instead of `var`). |
| `custom_types` | | A map which controls how custom types are exposed to Kotlin. See the [custom types section of the manual](../udl/custom_types.md#custom-types-in-the-bindings-code)|
| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../udl/remote_ext_types.md#kotlin)
| `custom_types` | | A map which controls how custom types are exposed to Kotlin. See the [custom types section of the manual](../types/custom_types.md#custom-types-in-the-bindings-code)|
| `external_packages` | | A map of packages to be used for the specified external crates. The key is the Rust crate name, the value is the Kotlin package which will be used referring to types in that crate. See the [external types section of the manual](../types/remote_ext_types.md#kotlin)
| `android` | `false` | Used to toggle on Android specific optimizations
| `android_cleaner` | `android` | Use the [`android.system.SystemCleaner`](https://developer.android.com/reference/android/system/SystemCleaner) instead of [`java.lang.ref.Cleaner`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ref/Cleaner.html). Fallback in both instances is the one shipped with JNA.
| `kotlin_target_version` | `"x.y.z"` | When provided, it will enable features in the bindings supported for this version. The build process will fail if an invalid format is used.
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/src/kotlin/gradle.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The generated bindings should appear in the project sources in Android Studio.
## Using experimental unsigned types

Unsigned integers in the defined API are translated to their equivalents in the foreign language binding, e.g. `u32` becomes Kotlin's `UInt` type.
See [Built-in types](../udl/builtin_types.md).
See [Built-in types](../types/builtin_types.md).

However unsigned integer types are experimental in Kotlin versions prior to 1.5.
As such they require explicit annotations to suppress warnings.
Expand Down
14 changes: 14 additions & 0 deletions docs/manual/src/proc_macro/docstrings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Docstrings

In proc-macros, Rust docstrings will be captured and rendered in the bindings.

For example:
```rust
/// This is the docstring for MyObject
#[derive(uniffi::Object)]
pub struct MyObject {}
```

Will cause Python, Swift and Kotlin to all generate a wrapper for `MyObject` with appropriate docstrings for that language.

You can see examples of how they are rendered in the [UDL docstrings documentation](../udl/docstrings.md)
82 changes: 82 additions & 0 deletions docs/manual/src/proc_macro/enumerations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
## The `uniffi::Enum` derive

The `Enum` derive macro works much like the [`Record`](./records.md) derive macro. Any fields inside variants must
be named. All types that are supported as parameter and return types by `#[uniffi::export]` are
also supported as field types.

It is permitted to use this macro on a type that is also defined in the UDL file as long as the
two definitions are equal in the names and ordering of variants and variant fields, and any field
types inside variants are UniFFI builtin types; user-defined types might be allowed in the future.

```rust
#[derive(uniffi::Enum)]
pub enum MyEnum {
Fieldless,
WithFields {
foo: u8,
bar: Vec<i32>,
},
WithValue = 3,
}
```

### Variant Discriminants

Variant discriminants are accepted by the macro but how they are used depends on the bindings.

For example this enum:

```rust
#[derive(uniffi::Enum)]
pub enum MyEnum {
Foo = 3,
Bar = 4,
}
```

would give you in Kotlin & Swift:

```swift
// kotlin
enum class MyEnum {
FOO,
BAR;
companion object
}
// swift
public enum MyEnum {
case foo
case bar
}
```

which means you cannot use the platforms helpful methods like `value` or `rawValue` to get the underlying discriminants. Adding a `repr` will allow the type to be defined in the foreign bindings.

For example:

```rust
// added the repr(u8), also u16 -> u64 supported
#[repr(u8)]
#[derive(uniffi::Enum)]
pub enum MyEnum {
Foo = 3,
Bar = 4,
}
```

will now generate:

```swift
// kotlin
enum class MyEnum(val value: UByte) {
FOO(3u),
BAR(4u);
companion object
}

// swift
public enum MyEnum : UInt8 {
case foo = 3
case bar = 4
}
```
50 changes: 50 additions & 0 deletions docs/manual/src/proc_macro/errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# The `uniffi::Error` derive

The `Error` derive registers a type as an error and can be used on any enum that the `Enum` derive also accepts.
By default, it exposes any variant fields to the foreign code.
This type can then be used as the `E` in a `Result<T, E>` return type of an exported function or method.
The generated foreign function for an exported function with a `Result<T, E>` return type
will have the result's `T` as its return type and throw the error in case the Rust call returns `Err(e)`.

```rust
#[derive(uniffi::Error)]
pub enum MyError {
MissingInput,
IndexOutOfBounds {
index: u32,
size: u32,
}
// tuple-enums work.
Generic(String),
}

#[uniffi::export]
fn do_thing() -> Result<(), MyError> {
// ...
}
```

You can also use the helper attribute `#[uniffi(flat_error)]` to expose just the variants but none of the fields.
In this case the error will be serialized using Rust's `ToString` trait
and will be accessible as the only field on each of the variants.
The types of the fields can be any UniFFI supported type and don't need to implement any special traits.

```rust
#[derive(uniffi::Error)]
#[uniffi(flat_error)]
pub enum MyApiError {
Http(reqwest::Error),
Json(serde_json::Error),
}

// ToString is not usually implemented directly, but you get it for free by implementing Display.
// This impl could also be generated by a proc-macro, for example thiserror::Error.
impl std::fmt::Display for MyApiError {
// ...
}

#[uniffi::export]
fn do_http_request() -> Result<(), MyApiError> {
// ...
}
```
90 changes: 90 additions & 0 deletions docs/manual/src/proc_macro/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Functions, Constructors, Methods

Functions are exported to the namespace with the `#[uniffi::export]` attribute

```rust
#[uniffi::export]
fn hello_world() -> String {
"Hello World!".to_owned()
}
```

All our owned types can be used as arguments and return types.

Arguments and receivers can also be references to these types, for example:

```rust
// Input data types as references
#[uniffi::export]
fn process_data(a: &MyRecord, b: &MyEnum, c: &Option<MyRecord>) {
...
}
```

To export methods of an interface you can use the [`#[uniffi::export]` attribute on an impl block](./interfaces.md).

## Default values

Exported functions/methods can have default values using the `default` argument of the attribute macro that wraps them.
`default` inputs a comma-separated list of `[name]=[value]` items.

```rust
#[uniffi::export(default(text = " ", max_splits = None))]
pub fn split(
text: String,
sep: String,
max_splits: Option<u32>,
) -> Vec<String> {
...
}

#[derive(uniffi::Object)]
pub struct TextSplitter { ... }

#[uniffi::export]
impl TextSplitter {
#[uniffi::constructor(default(ignore_unicode_errors = false))]
fn new(ignore_unicode_errors: boolean) -> Self {
...
}

#[uniffi::method(default(text = " ", max_splits = None))]
fn split(
text: String,
sep: String,
max_splits: Option<u32>,
) -> Vec<String> {
...
}
}
```

Supported default values:
- String, integer, float, and boolean literals
- `[]` for empty Vecs
- `Option<T>` allows either `None` or `Some(T)`

### Renaming functions, methods and constructors

A single exported function can specify an alternate name to be used by the bindings by specifying a `name` attribute.

```rust
#[uniffi::export(name = "something")]
fn do_something() {
}
```
will be exposed to foreign bindings as a namespace function `something()`

You can also rename constructors and methods:
```rust
#[uniffi::export]
impl Something {
// Set this as the default constructor by naming it `new`
#[uniffi::constructor(name = "new")]
fn make_new() -> Arc<Self> { ... }

// Expose this as `obj.something()`
#[uniffi::method(name = "something")]
fn do_something(&self) { }
}
```
Loading

0 comments on commit 4cbb8f1

Please sign in to comment.