8000 docs(spec): UDF traits need uniffi::export(with_foreign) (#180) by adityamukho · Pull Request #756 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/superpowers/plans/2026-10-24-180-udf-uniffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Worktree `.worktrees/180-udf-ffi` (reused), branch `feat/180-fallible-udfs` from
Branch `feat/180-udf-ffi` from `minigraf-binding-template` `v3`.

14. **Pin** `minigraf` to PR 1's merge commit (git rev, as the other v3 pins).
15. **FFI code** (spec §6) in `src/lib.rs`: `MiniGrafUdfError` with `From<uniffi::UnexpectedUniFFICallbackError>`, the three traits under `#[uniffi::export]`, and `MiniGrafDb::register_predicate`/`register_aggregate` over `try_register_*` with `Acc = Arc<dyn MiniGrafAccumulator>`. A `Ref` from `finish` that is not a UUID becomes `UdfError` with `to_core`'s message. The handle lock change (spec §6.1 "Handle locks"): every `MiniGrafDb` method locks only to clone the `Minigraf`. Rust tests with Rust implementations of the traits: a predicate, an aggregate in `:find` and `:over`, a failing `step` → `MiniGrafError::Query` containing `QRY-014`, a bad `Ref` from `finish`, and a predicate that queries the same `MiniGrafDb`.
15. **FFI code** (spec §6) in `src/lib.rs`: `MiniGrafUdfError` with `From<uniffi::UnexpectedUniFFICallbackError>`, the three traits under `#[uniffi::export(with_foreign)]`, and `MiniGrafDb::register_predicate`/`register_aggregate` over `try_register_*` with `Acc = Arc<dyn MiniGrafAccumulator>`. A `Ref` from `finish` that is not a UUID becomes `UdfError` with `to_core`'s message. The handle lock change (spec §6.1 "Handle locks"): every `MiniGrafDb` method locks only to clone the `Minigraf`. Rust tests with Rust implementations of the traits: a predicate, an aggregate in `:find` and `:over`, a failing `step` → `MiniGrafError::Query` containing `QRY-014`, a bad `Ref` from `finish`, and a predicate that queries the same `MiniGrafDb`.
16. **Bindgen check**: generate Python, Kotlin and Swift from the built cdylib (README step 5) and confirm that each has an implementable interface for all three traits (Python: a class to subclass; Kotlin: an `interface`; Swift: a `protocol`), that throwing `MiniGrafUdfError` is declared, and note Swift's `Sendable` requirement if the protocol carries one. Put the findings in the README's §6.1 section (lifecycle, threading, cost, re-entry), which every binding copies.

## PRs 3–6: one per binding (python, java, android, swift)
Expand Down
8 changes: 4 additions & 4 deletions docs/superpowers/specs/2026-10-24-udf-uniffi-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,18 @@ pub enum MiniGrafUdfError {
}
impl From<uniffi::UnexpectedUniFFICallbackError> for MiniGrafUdfError { /* Failed { msg: reason } */ }

#[uniffi::export]
#[uniffi::export(with_foreign)]
pub trait MiniGrafPredicate: Send + Sync {
fn test(&self, value: MiniGrafValue) -> Result<bool, MiniGrafUdfError>;
}

#[uniffi::export]
#[uniffi::export(with_foreign)]
pub trait MiniGrafAggregate: Send + Sync {
/// A new, empty accumulator: one per group, or per window partition.
fn accumulator(&self) -> Result<Arc<dyn MiniGrafAccumulator>, MiniGrafUdfError>;
}

#[uniffi::export]
#[uniffi::export(with_foreign)]
pub trait MiniGrafAccumulator: Send + Sync {
fn step(&self, value: MiniGrafValue) -> Result<(), MiniGrafUdfError>;
/// `count`: values stepped so far.
Expand All @@ -133,7 +133,7 @@ impl MiniGrafDb {
}
```

- The traits are exported with plain `#[uniffi::export]`: UniFFI 0.31 lets foreign code implement an exported trait taken as `Arc<dyn Trait>`, and `with_foreign` is a deprecated alias for that. The plan checks that each target language (Python, Kotlin, Swift) generates an implementable interface.
- The traits are exported with `#[uniffi::export(with_foreign)]`. Without it UniFFI 0.31.1 generates no callback interface: Python, Kotlin and Swift get only the Rust-backed class, and `register_*` accepts nothing else. (An earlier draft of this spec said plain `#[uniffi::export]` was enough. The template's bindgen check, plan step 16, showed it was not.) With it, Python subclasses `MiniGrafPredicate`, Kotlin implements `interface MiniGrafPredicate` (the Rust-backed class is `MiniGrafPredicateImpl`), and Swift conforms to `protocol MiniGrafPredicate: AnyObject, Sendable`.
- The binding implements these with `try_register_predicate`/`try_register_aggregate`. `Acc` is `Arc<dyn MiniGrafAccumulator>`. `MiniGrafUdfError::Failed { msg }` becomes `UdfError::new(msg)`.
- A `finish` that returns a `Ref` whose value is not a UUID is a failing call: ``ref value "<s>" is not a UUID`` (the same text `to_core` gives).
- `count` is `i64`, following the bindings' rule that counts cross the FFI signed (Kotlin's unsigned types cannot be called from Java).
Expand Down
Loading
0