|
| 1 | +# One-Call Polyglot Exports Design |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Equilibrium should make polyglot FFI feel like one library call. A user should be able to point Equilibrium at a source file and get a C ABI artifact plus usable bindings without needing to hand-write a build graph or run the CLI. |
| 6 | + |
| 7 | +The default API remains library-first: |
| 8 | + |
| 9 | +```rust |
| 10 | +let module = equilibrium_ffi::load("src/native/math.rs")?; |
| 11 | +``` |
| 12 | + |
| 13 | +That call should detect the source language, build the C ABI output, generate bindings or import metadata, and return a module handle that tells the caller what was produced. |
| 14 | + |
| 15 | +## Export Discovery |
| 16 | + |
| 17 | +Equilibrium should prefer explicit export markers when they exist: |
| 18 | + |
| 19 | +- Rust functions marked with the `equilibrium-rust` macro. |
| 20 | +- Zig functions declared with `pub export`. |
| 21 | +- Nim procedures using C export pragmas or Equilibrium helpers. |
| 22 | +- D functions declared with `extern(C) export` or marked by the helper attribute. |
| 23 | +- C and C++ declarations already present in a header. |
| 24 | + |
| 25 | +If no explicit markers are found and the caller did not provide an export list in the API call or `equilibrium.toml`, Equilibrium should export every top-level function in that source file. |
| 26 | + |
| 27 | +That fallback should be intentionally simple. It is a convenience path for small files and demos, not a promise to infer complex language-specific visibility rules across an entire project. |
| 28 | + |
| 29 | +## API Shape |
| 30 | + |
| 31 | +The one-call path should support three levels of control: |
| 32 | + |
| 33 | +```rust |
| 34 | +equilibrium_ffi::load("src/native/math.zig")?; |
| 35 | +``` |
| 36 | + |
| 37 | +This uses detection, explicit markers if present, and all-functions fallback when no markers exist. |
| 38 | + |
| 39 | +```rust |
| 40 | +equilibrium_ffi::load_with_options( |
| 41 | + "src/native/math.zig", |
| 42 | + LoadOptions::default().exports(["add", "multiply"]), |
| 43 | +)?; |
| 44 | +``` |
| 45 | + |
| 46 | +This uses caller-provided exports and skips implicit all-function export. |
| 47 | + |
| 48 | +```toml |
| 49 | +[target.math] |
| 50 | +language = "zig" |
| 51 | +sources = ["src/native/math.zig"] |
| 52 | +exports = ["add", "multiply"] |
| 53 | +``` |
| 54 | + |
| 55 | +This gives repeatable project configuration when the caller wants it. |
| 56 | + |
| 57 | +## Artifact Model |
| 58 | + |
| 59 | +Every supported language should lower to the same artifact model: |
| 60 | + |
| 61 | +- source language |
| 62 | +- generated C ABI library or object |
| 63 | +- generated or discovered C header |
| 64 | +- generated consumer bindings |
| 65 | +- discovered exports |
| 66 | +- warnings |
| 67 | + |
| 68 | +Rust remains the first-class consumer because this crate is Rust, but the artifact model should not assume Rust is the only consumer. Zig, Nim, D, and future helpers can import the same C ABI surface through generated wrappers. |
| 69 | + |
| 70 | +## CLI Role |
| 71 | + |
| 72 | +The CLI should stay optional. It can generate bindings, inspect projects, or precompute a build graph, but the default workflow should not require it. |
| 73 | + |
| 74 | +Useful CLI command surfaces: |
| 75 | + |
| 76 | +```bash |
| 77 | +eq generate src/native/math.zig |
| 78 | +eq graph |
| 79 | +eq build-graph |
| 80 | +``` |
| 81 | + |
| 82 | +These commands should expose the same behavior as the library, not define a separate architecture. |
| 83 | + |
| 84 | +## Build Time and Runtime |
| 85 | + |
| 86 | +The same export discovery and artifact generation should work from `build.rs` and at runtime: |
| 87 | + |
| 88 | +- In `build.rs`, generated artifacts should land in Cargo's output directory or a caller-provided directory. |
| 89 | +- At runtime, generated artifacts should land in a cache directory or caller-provided directory. |
| 90 | +- Outputs should be deterministic enough that repeated calls can reuse existing artifacts when inputs have not changed. |
| 91 | + |
| 92 | +## Error Handling |
| 93 | + |
| 94 | +Errors should say which stage failed: |
| 95 | + |
| 96 | +- language detection |
| 97 | +- export discovery |
| 98 | +- compiler discovery |
| 99 | +- C ABI artifact generation |
| 100 | +- header generation |
| 101 | +- binding generation |
| 102 | +- dynamic loading |
| 103 | + |
| 104 | +Implicit all-function export should produce warnings when a function cannot be represented safely in the C ABI instead of silently creating invalid bindings. |
| 105 | + |
| 106 | +## Testing |
| 107 | + |
| 108 | +The implementation should add focused coverage for: |
| 109 | + |
| 110 | +- explicit export marker detection |
| 111 | +- all-functions fallback when no markers are present |
| 112 | +- API-provided export list overriding fallback |
| 113 | +- `equilibrium.toml` export list overriding fallback |
| 114 | +- generated artifact metadata |
| 115 | +- at least one Rust-to-Zig or Zig-to-Rust consumer wrapper path through the C ABI |
| 116 | + |
| 117 | +Compiler-dependent integration tests should skip cleanly when a host compiler is missing. |
0 commit comments