# wit/rust-calls-lisp -- a Rust program calling a Lisp component

The mirror image of [`wit/lisp-calls-rust/`](https://github.com/making/rontolisp/blob/develop/examples/wit/lisp-calls-rust). There a Lisp
program called Rust; here a **Rust** component calls a **Lisp** one. The Lisp
component exports a plain `vowel-count` function; the Rust component imports it,
and [`wac`](https://github.com/bytecodealliance/wac) composes the two into one
runnable component.

```
   describe("hello world")         rust-describer                counter.lisp
   (host --invoke)                 (Rust reactor)                (Lisp reactor)
                                   --------------                --------------
                            ─────▶ export describe
                                   import vowel-count  ────────▶ export vowel-count
                                                      ◀────────  counts: 3
                                   format!("... {} vowels")
   "\"hello world\" has 3 vowels" ◀───────────────────────────
```

`describe` is Rust's job (building the sentence); `vowel-count` is Lisp's job. The
host calls `describe`; nothing in the Rust code knows the counter is written in
Lisp -- only the WIT does.

## Prerequisites

- a rontolisp binary (or the executable JAR -- set `RONTOLISP` accordingly)
- a Rust toolchain with the `wasm32-unknown-unknown` target
  (`rustup target add wasm32-unknown-unknown`)
- [`cargo-component`](https://github.com/bytecodealliance/cargo-component)
  (`cargo install cargo-component`) to build the Rust component
- [`wac`](https://github.com/bytecodealliance/wac) (`cargo install wac-cli`) and
  `wasmtime` 46+ (plus `wasm-tools` if you want to inspect the components)

[`build.sh`](build.sh) runs it all end to end:

```bash
RONTOLISP=../../../target/rontolisp ./build.sh
# "\"hello world\" has 3 vowels"
```

## 1. The contract

[`wit/vowels.wit`](wit/vowels.wit) declares the interface Rust exports and the
plain function it imports:

```wit
package example:vowels;

interface describe {
  describe: func(text: string) -> string;
}

world describer {                                  // the Rust component
  export describe;
  import vowel-count: func(text: string) -> s32;
}

world counter {                                    // the Lisp component
  export vowel-count: func(text: string) -> s32;
}
```

`vowel-count` is a **plain function**, not an interface, because
`rontolisp:wit-export` implements plain function exports -- so the Rust world
imports it as a bare function, matching what Lisp exports.

## 2. The Lisp component -- exports the function

[`counter.lisp`](counter.lisp) implements the `counter` world:

```lisp
(defun vowel-count (text)
  (let ((n 0))
    (dotimes (i (length text))
      (when (vowelp (char text i)) (incf n)))
    n))

(rontolisp:wit-export "wit/vowels.wit" :world counter)
```

```bash
rontolisp counter.lisp -o counter.wasm --component --optimize
```

A reactor component that exports `vowel-count: func(text: string) -> s32`.

## 3. The Rust component -- imports it

The crate was scaffolded with [`cargo-component`](https://github.com/bytecodealliance/cargo-component)
(`cargo install cargo-component`):

```bash
cargo component new --lib rust-describer --name describer --namespace example
```

Delete the crate-local `wit/` it writes and point [`Cargo.toml`](rust-describer/Cargo.toml)
at the **shared** WIT instead:

```toml
[package.metadata.component.target]
path = "../wit"
world = "describer"
```

Then fill in [`src/lib.rs`](rust-describer/src/lib.rs), calling the imported
`vowel_count` like any other function (`mod bindings` is generated by
cargo-component from that WIT):

```rust
#[allow(warnings)]
mod bindings;
use bindings::exports::example::vowels::describe::Guest;
use bindings::vowel_count;

struct Component;
impl Guest for Component {
    fn describe(text: String) -> String {
        let n = vowel_count(&text);                // Rust -> Lisp
        let plural = if n == 1 { "" } else { "s" };
        format!("\"{text}\" has {n} vowel{plural}")
    }
}
bindings::export!(Component with_types_in bindings);
```

It imports no WASI, so a `wasm32-unknown-unknown` build **is** the component --
cargo-component runs the componentization, no adapter:

```bash
cd rust-describer && cargo component build --release --target wasm32-unknown-unknown && cd ..
cp rust-describer/target/wasm32-unknown-unknown/release/describer.wasm describer.wasm
```

## 4. Compose and run

`wac plug` fills the Rust describer's `vowel-count` import with the Lisp
component's matching export:

```bash
wac plug describer.wasm --plug counter.wasm -o vowels.wasm
```

`vowels.wasm` exports `describe` and no longer imports `vowel-count` -- it is
wired inside. The host calls the export by name with a
[WAVE](https://component-model.bytecodealliance.org/) argument:

```bash
wasmtime run -W gc=y --invoke 'describe("hello world")' vowels.wasm
```
```console
"\"hello world\" has 3 vowels"
```

The result is a `string`, so `wasmtime` prints it quoted (the inner quotes are
the ones `describe` put around the phrase). `describe("a")` answers
`"\"a\" has 1 vowel"` — the count that picked the singular came from the Lisp
component.

## Notes

Only `string` and an `s32` cross the boundary -- the flat, no-`result` subset, so
no `-W exceptions=y` is needed. The Rust component is a reactor invoked by the
host; making Rust a printing command instead would pull in WASI on the Rust side,
which is a separate concern from the cross-language call this example is about.
The full type mapping is documented in
[`rontolisp:wit-export`](https://github.com/making/rontolisp/blob/develop/examples/wit/doc/en/reference/functions/rontolisp-wit-export.md).
