Skip to content

Commit

Permalink
Partial support for duplicated symbols (#143)
Browse files Browse the repository at this point in the history
 According to [The Big O of Code
Reviews](https://www.egorand.dev/the-big-o-of-code-reviews/), this is a
O(_n_) change.

This PR partially fixes #68:

- it re-exports each named crate as default export.
- it documents working with megazords.
- including the limitations of the Swift backend.

The second part to fix #68 is a little more involved, but is not yet
worth it:

- for imported types and their converters, the bindings should use a
more fully qualified name. e.g.

Instead of:

```ts
import { Crate1 } from "./crate1";
import crate1 from "./crate1";

const { FfiConverterTypeCrate1 } = crate1.converters;

// In use
type MyType = {
  scalar: Crate1,
  optional: Crate1 | undefined,
  list: Array<Crate1>,
}

// FfiConverterTypeCrate1 being used.
const lift = FfiConverterTypeCrate1.lift.bind(FfiConverterTypeCrate1);
```

The next part needs to generate code:

```ts
import * as crate1 from "./crate1";

// In use
type MyType = {
  scalar: crate1.Crate1,
  optional: crate1.Crate1 | undefined,
  list: Array<crate1.Crate1>,
}

// FfiConverterTypeCrate1 being used.
const lift = crate1.converters.FfiConverterTypeCrate1.lift.bind(crate1.converters.FfiConverterTypeCrate1);
```

I think I will file a new issue with this in, and close #68.

---------

Co-authored-by: Johannes Marbach <[email protected]>
  • Loading branch information
jhugman and Johennes authored Oct 25, 2024
1 parent 0486689 commit c7bb0c3
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 3 deletions.
18 changes: 15 additions & 3 deletions crates/ubrn_cli/src/codegen/templates/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@ installer.installRustCrate();
export * from './{{ bindings }}/{{ m.ts() }}';
{%- endfor %}

// Now import the bindings so we can:
// - intialize them
// - export them as namespaced objects as the default export.
{%- for m in self.config.modules %}
import * as {{ m.ts() }} from './{{ bindings }}/{{ m.ts() }}';
{%- endfor %}

// Initialize the generated bindings: mostly checksums, but also callbacks.
{%- for m in self.config.modules %}
import {{ m.ts() }}_ from './{{ bindings }}/{{ m.ts() }}';
{{ m.ts() }}.default.initialize();
{%- endfor %}
{% for m in self.config.modules %}
{{ m.ts() }}_.initialize();

// Export the crates as individually namespaced objects.
export default {
{%- for m in self.config.modules %}
{{ m.ts() }},
{%- endfor %}
};

{# space #}
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Before you start](guides/pre-installation.md)
- [Step by step: Make your first library project](guides/getting-started.md)
- [Publishing your library project](guides/publishing.md)
- [Working with multiple crates in one library](guides/megazords.md)

# Mapping Rust on to Typescript

Expand Down
55 changes: 55 additions & 0 deletions docs/src/guides/megazords.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Working with multiple crates in one library

Some teams arrange their Rust library in to multiple crates, or multiple teams from one organization combine their efforts into one library.

This might be for better code organization, or to reduce shipping multiple copies of the same dependencies.

The combined library from multiple crates, in Mozilla vernacular, is known as a [Megazord](https://robots.fandom.com/wiki/Mighty_Morphin%27_Megazord).

`uniffi-rs` and `uniffi-bindgen-react-native` both work well with Megazords.

`uniffi-bindgen-react-native` produces a cluster of files per crate. For example, generating files from the library `libmymegazord.a` might contain two crates, `crate1` and `crate2`. The library directory would look like this:

```
cpp
├── generated
│ ├── crate1.cpp
│ ├── crate1.hpp
│ ├── crate2.cpp
│ └── crate2.hpp
├── react-native-my-megazord.cpp
└── react-native-my-megazord.h
src
├── NativeMyMegazord.ts
├── generated
│ ├── crate1.ts
│ ├── crate1-ffi.ts
│ ├── crate2.ts
│ └── crate2-ffi.ts
└── index.tsx
```

In `index.tsx`, the types are re-exported from `crate1.ts` and `crate2.ts`.

In this extended example, `crate1.ts` might declare a `Crate1Type` and `crate2.ts` a `Crate2Type`.

In this case, your library's client code would import `Crate1Type` and `Crate2Type` like this:

```ts
import { Crate1Type, Crate2Type } from "react-native-my-megazord";
```

Alternatively, they can use the default export:

```ts
import megazord from "react-native-my-megazord";

const { Crate1Type } = megazord.crate1;
const { Crate2Type } = megazord.crate2;
```

```admonish warning title="Duplicated identifiers"
Due to Swift's large granular module sytem, crates in the same megazord cannot have types of the same name.
This may be solved in Swift at some point— e.g. by adding prefixes— but until then, duplicate identifiers will cause a Typescript compilation error as the types are smooshed together in `index.tsx`.
```
38 changes: 38 additions & 0 deletions typescript/tests/importing-qualified.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/
*/

import * as exported from "./playground/exported";

import { test } from "../testing/asserts";

test("Records imported as type", (t) => {
const record: exported.MyRecord = {
prop1: "string",
prop2: 42,
};
});

test("Enums imported as objects", (t) => {
const enum_: exported.MyEnum = new exported.MyEnum.Variant1();
});

test("Objects interfaces imported as types", (t) => {
// In our generated code, `MyObject` would not be imported into this
// file because all creation happens via the `FfiConverterTypeMyObject`.
const obj: exported.MyObjectInterface = new exported.MyObject();
});

test("Callback interfaces imported as types", (t) => {
class Impl implements exported.MyCallbackInterface {
myMethod(): void {}
}

const cb = new Impl();
});

test("Custom types imported as types", (t) => {
const s: exported.MyCustomString = "string";
});

0 comments on commit c7bb0c3

Please sign in to comment.