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

[`wit/keyvalue/`](https://github.com/making/rontolisp/blob/develop/examples/wit/keyvalue) calls a WIT interface that a **host** implements.
This directory calls one that **another guest language** implements: a Lisp
command imports `example:textkit/casing`, a Rust component exports it, and
[`wac`](https://github.com/bytecodealliance/wac) composes the two into one
runnable component. The `.wit` is the only thing the two languages share.

```
   app.lisp                        rust-shouter
   (Lisp command)                  (Rust reactor)
   --------------                  --------------
   tk:shout("hello world")  ─────▶ export casing.shout
                                   "hello world".to_uppercase() + "!"
   "HELLO WORLD!" ◀────────────────
```

For the reverse direction see
[`wit/rust-calls-lisp/`](https://github.com/making/rontolisp/blob/develop/examples/wit/rust-calls-lisp). The app also runs **standalone** on
the interpreter and JVM, where a small Lisp `rontolisp:wit-provide` answers the
same interface (§2) — develop against the Lisp fallback, deploy against Rust,
and never touch the program in between.

## 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  ->  HELLO WORLD!
# component model  ->  COMPONENT MODEL!
# rust and lisp  ->  RUST AND LISP!
```

## 1. The interface

[`wit/textkit.wit`](wit/textkit.wit):

```wit
package example:textkit;

interface casing {
  shout: func(text: string) -> string;
}
```

## 2. The Lisp app -- imports it, and provides a fallback

[`app.lisp`](app.lisp) binds the interface's functions with `rontolisp:wit-import`
(package `tk`), then calls `tk:shout`:

```lisp
(rontolisp:wit-import "wit/textkit.wit"
                      :interface "example:textkit/casing"
                      :package tk)

(dolist (phrase '("hello world" "component model" "rust and lisp"))
  (format t "~a  ->  ~a~%" phrase (tk:shout phrase)))
```

On the interpreter and JVM there is no Rust component to call, so a `wit-import`
needs a **provider** -- an ordinary Lisp callable, bound with
`rontolisp:wit-provide`, that takes the member name (`"shout"`) and the call's
arguments. A two-line lambda makes the file run **standalone**:

```lisp
(rontolisp:wit-provide "example:textkit/casing"
                       #'(lambda (member &rest args)
                           (cond ((string= member "shout")
                                  (concatenate 'string (string-upcase (first args)) "!"))
                                 (t (error "casing: unknown member ~a" member)))))
```

So the same file runs three ways and prints the same thing each time:

```bash
rontolisp app.lisp                                       # interpreter: the Lisp provider answers
rontolisp app.lisp -o App.class && java -cp . App        # JVM: the same provider
rontolisp app.lisp -o app.wasm --component --optimize    # imports the interface instead
```

On WASM a `wit-provide` is **inert**, so the composed Rust component answers
there: on the interpreter and JVM this is Lisp calling Lisp, composed it is Lisp
calling Rust.

## 3. The Rust component -- exports it

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

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

That writes `rust-shouter/{Cargo.toml, src/lib.rs}` and a starter `wit/world.wit`.
Two edits turn the skeleton into this example: delete the crate-local `wit/` and
point [`Cargo.toml`](rust-shouter/Cargo.toml) at the **shared** WIT instead, so
the two sides cannot drift --

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

-- then fill in the one export in [`src/lib.rs`](rust-shouter/src/lib.rs) (the
`mod bindings` is generated by cargo-component from that WIT):

```rust
#[allow(warnings)]
mod bindings;
use bindings::exports::example::textkit::casing::Guest;

struct Component;
impl Guest for Component {
    fn shout(text: String) -> String {
        format!("{}!", text.to_uppercase())
    }
}
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 and no separate
`wasm-tools component new`:

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

## 4. Compose and run

`wac plug` fills the app's import with the Rust component's matching export,
pairing them by their WIT type:

```bash
wac plug app.wasm --plug shouter.wasm -o textkit.wasm
wasmtime run -W gc=y textkit.wasm
```
```console
hello world  ->  HELLO WORLD!
component model  ->  COMPONENT MODEL!
rust and lisp  ->  RUST AND LISP!
```

## Notes

Only `string` crosses here, in both directions — the flat, no-`result` subset of
rontolisp's WIT mapping, which is why no `-W exceptions=y` is needed. Full
mapping:
[`rontolisp:wit-import`](https://github.com/making/rontolisp/blob/develop/examples/wit/doc/en/reference/functions/rontolisp-wit-import.md).
