# Rust Embedding Boundary
This reference defines the generated binding, runtime ownership, and data
contracts behind [Rust embedding](../embedding.md). The managed workflow owns
project selection and typed declarations; the Rust application still owns the
loaded execution, capabilities, mutable state, Echo storage, and call order.
## Generated Project And Functions
`geam embedding sync` generates one `src/geam_bindings.rs` module from the
same-name public Gleam module under `gleam/src/`. The generated module contains:
- the conventional nested-project selection;
- a typed `Functions` aggregate for supported public functions;
- binding code that connects those declarations to one loaded module; and
- static built-in and external provider composition required by the selected
source closure.
It does not contain compiled Gleam bodies, runtime state, credentials, output
destinations, or application policy. Generated bytes are deterministic and are
expected to be reviewed and committed.
Unsupported public declarations fail synchronization with the function and
nested type position. They are not silently omitted from `Functions`.
## Project Loading
Generated bindings expose one loading shape whether the selected source closure
is provider-free or hosted:
```rust
let program = geam_bindings::project().compile()?;
```
`project()` only constructs a project selection. When the source closure uses
built-in or external providers, `compile()` performs the generated static
provider registration before loading the selected Gleam source. Registration
and project-loading failures remain distinguishable through the returned error.
Provider-free and hosted loading both read `gleam.toml`, `manifest.toml`, root
source, and locked package source from the conventional `gleam/` directory.
They do not invoke Gleam CLI, download packages, or rewrite project files at
runtime.
The lower-level `compile_typed_project` and `compile_typed_host_project`
functions remain available when an application deliberately owns source
selection or provider registration instead of generated bindings.
## Binding And Sealing
The provider-free lifecycle is:
```rust
use geam::embedding::ModuleBuilder;
let program = geam_bindings::project().compile()?;
let builder = ModuleBuilder::from_program(program)?;
let (bindings, functions) = geam_bindings::bind(builder)?;
let module = bindings.seal();
```
Generated binding consumes the builder and returns typed handles associated
with that exact owner. Sealing verifies that required declarations and host
specializations are complete before calls begin.
The application should retain the sealed module and `Functions` for repeated
calls. A handle or retained runtime value belongs to one loaded owner even when
another load uses identical source and signatures.
## Hosted State
When the source closure requires providers, generated `RunStateInputs` lists
every capability and configuration value the application must choose. For
example, an application using stdlib and one external provider can initialize:
```rust
let mut state = geam_bindings::RunStateInputs {
stdlib: GleamStdlibRunState::from_seed([7; 32]),
example_text_pattern: HostProviderConfiguration::empty(),
}
.initialize()?;
```
Time-backed source adds a caller-owned `time` input. Stateless unit components,
including JSON, are initialized internally and do not create synthetic input
fields. Initialization returns `RunState` directly when every selected
component is total and preserves `HostProviderInitializationError` when a
provider can reject configuration.
The complete lifecycle is:
1. Load and compile the generated project selection.
2. Build and bind every selected public function into one owner.
3. Seal that owner once.
4. Construct caller-owned capabilities, provider configuration, mutable state,
and Echo storage.
5. Reuse typed function handles and the sealed module for repeated calls.
The canonical [Rust embedding
application](https://github.com/panarch/geam/tree/main/examples/rust_embedding_application)
shows this hosted lifecycle with stdlib IO and the text-pattern provider.
## Data Grammar
Generated public function arguments and returns support this recursive grammar:
```text
| `Int` | `BigInt` |
| `Float` | `f64` |
| `String` | `EcoString` |
| `BitArray` | `BitArrayValue` |
| `UtfCodepoint` | `char` |
| `Bool` | `bool` |
| `Nil` | `()` |
| `#(A, ...)` | `(A, ...)` |
| prelude `Result(A, B)` | `Result<A, B>` |
| stdlib `Option(A)` | `Option<A>` |
| `List(A)` input | consumed `Vec<A>` or retained `&List<A>` |
| `List(A)` output | retained `List<A>` |
`BigInt`, `EcoString`, `BitArrayValue`, and embedding `List` are re-exported
from `geam::embedding`. Tuple values have one through seven elements. `(T,)` is
a one-element Gleam Tuple, while `()` is Gleam Nil. A function has zero through
seven source arguments passed as one Rust argument tuple; this arity is separate
from any Tuple-valued source argument.
All compound types recurse, including `List(List(String))` and Lists inside
Tuple, Result, or Option. Only the prelude Result and `gleam/option.Option` from
`gleam_stdlib` map to Rust's standard variants. Aliases resolving to those
types work; custom types with matching names or constructors do not.
A source-visible Gleam `Error` remains an ordinary Rust `Err` inside a function
return. It is separate from the outer `Result` returned by `module.call`, whose
`CallError` reports a foreign handle or value, invalid call ownership, or an
execution failure:
```rust
let rows: Vec<(EcoString, BigInt)> = vec![("invalid".into(), 2.into())];
let checked = module.call(
&functions.validate_batch,
(rows,),
&mut state,
&mut echo,
)?;
assert_eq!(checked.get(0), Some(Err("invalid code".into())));
```
## Retained Lists
A consumed `Vec` constructs a new Gleam List. A borrowed List from the same
loaded module reuses its retained handle without traversing or reconstructing
items:
```rust
let rows: Vec<(EcoString, BigInt)> = vec![
("AB-12".into(), 3.into()),
("invalid".into(), 2.into()),
];
let checked = module.call(
&functions.validate_batch,
(rows,),
&mut state,
&mut echo,
)?;
let total = module.call(
&functions.total_quantity,
(&checked,),
&mut state,
&mut echo,
)?;
```
The read-only List API makes materialization explicit:
- `len` and `is_empty` are O(1) and decode no items.
- `get` decodes one item and returns `None` for an out-of-range index.
- `iter` yields owned items lazily.
- `to_vec` decodes every item into a new Vec.
Retained Lists own immutable storage needed for reading. They remain readable
after the call, state, Echo, and module are dropped. They do not borrow or
recreate mutable provider state and do not implement `Send` or `Sync`.
Passing a retained List back is restricted to its original live module. A
different load is a different owner. `CallError::ForeignValue` is returned
before source execution or host-state mutation when ownership does not match.
For scalar or non-List compound items, `to_vec()` followed by fresh Vec input
transfers values to another owner. Nested Lists require explicit recursive
materialization:
```rust
A fresh outer Vec cannot contain retained children. `Vec<List<T>>` and
`Vec<&List<T>>` are not accepted because those children retain their original
owner.
## Input Inference
Generated bindings fix every non-List position and allow an independent input
carrier for each List position. Callers pass Vecs or borrowed Lists directly;
there is no public mode wrapper.
An absent Option or Result branch may not give Rust enough information to
select a List carrier. Use an ordinary local type annotation:
```rust
let rows: Option<Vec<(EcoString, BigInt)>> = None;
module.call(&functions.optional_batch, (rows,), &mut state, &mut echo)?;
```
## Gleam Boundary Modules
Arbitrary records and custom enums, external values, callbacks, public
constants, and generic signatures are not generated Rust binding types. Keep
those values in an imported Gleam module and expose a thin same-name root module
that projects them into the supported ordinary-data grammar.
The canonical application keeps normalization, validation, and its opaque
`Stock` type inside Gleam. Rust sees only batch validation, total quantity, and
first-valid-row operations. This preserves the source domain model while
keeping the cross-language ABI small.
## Echo And IO
Provider-free calls receive a caller-owned Echo sink. Hosted calls receive both
the generated mutable state and Echo sink. Official `gleam/io` writes through
the stdlib state selected by the application; it does not share a hidden queue
with Echo.
The application decides whether collected output is written to a terminal,
captured for tests, forwarded elsewhere, or ignored.
## Manual Binding
For provider-free direct control over source declarations and binding, the
[low-level embedding example](https://github.com/panarch/geam/blob/main/examples/rust_embedding.rs)
loads a no-`main` Gleam project, declares exact scalar signatures, seals one
shared execution, and calls its handles repeatedly.
Source closures that require built-in or external providers should normally use
the managed init and sync workflow. Applications that deliberately assemble a
provider profile can use the lower-level APIs documented in the
[host provider boundary](provider-boundary.md), but that is not a second
recommended project layout.
The current source-backed workflow reads the nested Gleam project at runtime;
see [compatibility](compatibility.md#runtime-and-deployment-limits) for the
self-contained deployment boundary.