diff --git a/README.md b/README.md
index 8e61ed1..693d87e 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,8 @@ a + b
**Execution context** — Rib source is **not** compiled into the guest component. In a REPL integration, Rib text is entered at an interactive prompt; it is parsed and interpreted **in the host process** (the binary that embeds the runtime, e.g. the Wasmtime CLI). That host then performs the actual **component calls** according to the embedder’s `invoke` implementation.
+**Instance identity** — Each bare `instance()` in Rib is a **new logical worker** (independent guest state). Passing the same string to `instance("…")` aliases the **same** worker. Integrators must map each worker name to **one** guest component instance for the session (see the [language guide](docs/language-guide.md#multiple-instance-calls-and-worker-identity) and [`rib-repl` README](rib-repl/README.md)).
+
---
## Repository layout
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index ca5724f..0000000
--- a/docs/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Documentation
-
-| Guide | Description |
-|-------|-------------|
-| [Rib language guide](https://golemcloud.github.io/rib/guide.html) | Syntax, patterns, and REPL-oriented usage; links to a highlighted [Example WIT](https://golemcloud.github.io/rib/example-wit.html) page in the same book. |
-| [`example.wit`](example.wit) | World `guide-demo`: `inventory` (records, enums, …) and `shopping` (`resource cart`); source in-repo, [readable in the book](https://golemcloud.github.io/rib/example-wit.html) with highlighting. |
-
-Formal grammar: [rib-lang/README.md](../rib-lang/README.md).
diff --git a/docs/language-guide.md b/docs/language-guide.md
index 19e2bed..c7c1345 100644
--- a/docs/language-guide.md
+++ b/docs/language-guide.md
@@ -71,7 +71,7 @@ Companion WIT — [`example.wit`](example.wit) exports `inventory` (records, enu
## Table of contents
0. [Example WIT (`example.wit`)](#0-example-wit-examplewit)
-1. [`instance()` and calling exports](#1-instance-and-calling-exports)
+1. [`instance()` and calling exports](#1-instance-and-calling-exports) (including [multiple calls and worker identity](#multiple-instance-calls-and-worker-identity))
2. [Programs, blocks, and semicolons](#2-programs-blocks-and-semicolons)
3. [Comments](#3-comments)
4. [Literals and Wave-shaped values](#4-literals-and-wave-shaped-values)
@@ -121,6 +121,33 @@ my-instance.lookup-sku(7)
That’s the whole pattern: one binding from `instance()`, then `that-name.export-name(…)`. Export names come from WIT and are usually kebab-case (`lookup-sku`, `format-stage`, …). Hyphens in `let` names are fine too (`store-main`, `lane-a`, …) if you prefer that style.
+
+
+### Multiple instance() calls and instance identity
+
+Each bare **`instance()`** (no argument) introduces a **new logical instance**: its own identity in the host, so **guest state is not shared** between different call sites. If a component keeps internal state behind an export (for example a counter), separate bindings behave like separate instances:
+
+```console
+>>> let x = instance()
+()
+>>> x.increment-and-get()
+1
+>>> x.increment-and-get()
+2
+>>> let y = instance()
+()
+>>> y.increment-and-get()
+1
+>>> y.increment-and-get()
+2
+```
+
+Here `x` and `y` are independent: `y`’s counter starts again at `1`.
+
+To **share** state across more than one Rib binding, give the **same worker name** as a string argument, e.g. `instance("my-worker")` for both bindings. The host maps that name to **one** live component instance for the session, so all calls using that name see the same state.
+
+**Embedder contract** — Runtimes that integrate Rib (Wasmtime, Golem, tests, …) must follow that meaning: **worker name is the identity key.** For each distinct name, keep one guest `Instance` (or equivalent) for the session (unless you explicitly reload). Two different names ⇒ two isolated instances; the **same** string passed to `instance("…")` ⇒ **one** underlying instance. Each anonymous `instance()` must receive a **unique** generated name so their state never collides.
+
More calls against [`example.wit`](example.wit) → `inventory`:
```rust
diff --git a/rib-lang/src/profile.rs b/rib-lang/src/profile.rs
index 79048d8..41e982e 100644
--- a/rib-lang/src/profile.rs
+++ b/rib-lang/src/profile.rs
@@ -11,6 +11,7 @@
// duplicate nested work (aggregate timers) are omitted from the table.
use std::cell::{Cell, RefCell};
+use std::cmp::Reverse;
use std::io::{stderr, IsTerminal};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;
@@ -188,7 +189,7 @@ fn print_summary_table(wall: Duration, title: &str) {
return;
}
- rows.sort_by(|a, b| b.1.cmp(&a.1));
+ rows.sort_by_key(|row| Reverse(row.1));
let s = Styles::detect();
let prefix = format!("{}[rib-profile]{} ", s.tag(), s.r());
diff --git a/rib-lang/src/type_inference/global_variable_type_binding.rs b/rib-lang/src/type_inference/global_variable_type_binding.rs
index 94ef234..836ffc5 100644
--- a/rib-lang/src/type_inference/global_variable_type_binding.rs
+++ b/rib-lang/src/type_inference/global_variable_type_binding.rs
@@ -84,21 +84,20 @@ fn override_type_arena(
for id in order {
let node = arena.expr(id);
match &node.kind {
- ExprKind::Identifier { variable_id } => {
- if variable_id == &spec.variable_id {
- current_path.progress();
- if spec.path.is_empty() {
- types.set(id, spec.inferred_type.clone());
- previous_id = None;
- current_path = full_path.clone();
- } else {
- previous_id = Some(id);
- }
- } else {
+ ExprKind::Identifier { variable_id } if variable_id == &spec.variable_id => {
+ current_path.progress();
+ if spec.path.is_empty() {
+ types.set(id, spec.inferred_type.clone());
previous_id = None;
current_path = full_path.clone();
+ } else {
+ previous_id = Some(id);
}
}
+ ExprKind::Identifier { .. } => {
+ previous_id = None;
+ current_path = full_path.clone();
+ }
ExprKind::SelectField {
expr: inner_id,
field,
diff --git a/rib-lang/src/type_inference/identify_instance_creation.rs b/rib-lang/src/type_inference/identify_instance_creation.rs
index e9ad8e1..cd36c03 100644
--- a/rib-lang/src/type_inference/identify_instance_creation.rs
+++ b/rib-lang/src/type_inference/identify_instance_creation.rs
@@ -38,26 +38,24 @@ fn search_for_invalid_instance_declarations_arena(
let node = arena.expr(id);
let span = node.source_span.clone();
match &node.kind.clone() {
- ExprKind::Let { variable_id, .. } => {
- if variable_id.name() == "instance" {
- return Err(CustomError::new(
- span,
- "`instance` is a reserved keyword and cannot be used as a variable.",
+ ExprKind::Let { variable_id, .. } if variable_id.name() == "instance" => {
+ return Err(CustomError::new(
+ span,
+ "`instance` is a reserved keyword and cannot be used as a variable.",
+ )
+ .into());
+ }
+ ExprKind::Identifier { variable_id }
+ if variable_id.name() == "instance" && variable_id.is_global() =>
+ {
+ return Err(CustomError::new(span, "`instance` is a reserved keyword")
+ .with_help_message(
+ "use `instance()` instead of `instance` to create an ephemeral instance.",
+ )
+ .with_help_message(
+ "for a named instance, use `instance(\"foo\")` where `\"foo\"` is the instance name",
)
.into());
- }
- }
- ExprKind::Identifier { variable_id } => {
- if variable_id.name() == "instance" && variable_id.is_global() {
- return Err(CustomError::new(span, "`instance` is a reserved keyword")
- .with_help_message(
- "use `instance()` instead of `instance` to create an ephemeral instance.",
- )
- .with_help_message(
- "for a named instance, use `instance(\"foo\")` where `\"foo\"` is the instance name",
- )
- .into());
- }
}
_ => {}
}
diff --git a/rib-repl/README.md b/rib-repl/README.md
index 8e0fd9a..e77af99 100644
--- a/rib-repl/README.md
+++ b/rib-repl/README.md
@@ -29,9 +29,11 @@ Example: Once the component is loaded, the following will result in `3`, if the
## Session semantics
-**Single component instance** — The embedding keeps **one** guest `Instance` (or equivalent) alive for the session unless it explicitly reloads. Each evaluated line runs against **that** instance, so guest state, linear memory where applicable, and **resource handles** tied to the instance remain coherent across prompts. This matches common REPL expectations: stateful APIs can be exercised incrementally without reinstantiating on every line.
+**One guest instance per Rib instance name** — Rib identifies a logical component instance by a **instance name**. A bare **`instance()`** in source gets a **fresh unique name** each time, so two bindings like `let x = instance(); let y = instance();` refer to **two independent** guest instances (separate state). To share state, use the same string for **`instance("name")`** on each binding that should alias the same guest.
-**Names from `let` carry across lines** — If you type `let x = …`, later lines can use **`x`** again, the same way variables work in any REPL. That is ordinary **`let`** behaviour, not a separate concept: the session keeps the values you already defined so you do not repeat large literals or constructor calls on every line.
+**Embedder responsibility** — Your `ComponentFunctionInvoke` (or equivalent) implementation must treat that worker name as the key: **one** Wasm `Instance` (or your runtime’s equivalent) per distinct name for the lifetime of the REPL session, unless you deliberately reload. Anonymous `instance()` calls must each map to a **unique** instance. This is how Wasmtime and other hosts preserve the semantics above.
+
+**Names from `let` carry across lines** — If you type `let x = …`, later lines can use **`x`** again, the same way variables work in any REPL. That is ordinary **`let`** behaviour: the session keeps the values you already defined so you do not repeat large literals or constructor calls on every line.
**Static checking** — `rib-lang` type-checks input against the component metadata registered for the session. Many errors surface as **Rib diagnostics** prior to any Wasm export call, reducing noisy trap-driven failures during exploration.
@@ -42,7 +44,7 @@ Example: Once the component is loaded, the following will result in `3`, if the
After load, component authors and runtime integrators routinely need to:
- Exercise exports with **realistic** `record`, `variant`, `list`, and `result` values without a new Rust `main` per experiment.
-- Retain **one instance** while calling constructors, methods, or other stateful exports in sequence.
+- Retain **one guest instance per worker name** while calling constructors, methods, or other stateful exports in sequence (several anonymous `instance()` calls ⇒ several independent instances).
- Reuse **[Wasm Wave](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave)**-compatible value text where possible instead of bespoke string formats.
`rib-repl` packages those requirements behind **`RibRepl::bootstrap`** / **`RibReplConfig`**: link the crate, implement **`ComponentFunctionInvoke`**, supply dependency loading, and obtain a `rustyline`-driven loop on top of the same **`rib`** pipeline tests may invoke directly.
@@ -60,7 +62,7 @@ After load, component authors and runtime integrators routinely need to:
- Syntax-highlighted input (`rustyline` and crate-local `RibEdit`).
- Tab completion for exports, variants, enums, and related symbols; **call completion** can insert **Wave-formatted argument lists** generated from each parameter’s **`WitType`** (see `value_generator.rs` and `rib_edit.rs`).
- Static typing of Rib source against the session’s component view.
-- Stateful sessions: one guest instance for the session. Example: `let x = instance(); let a = x.increment_and_get(); let b = x.increment_and_get(); a + b` will result in `3` if the component exports a stateful `increment_and_get` method.
+- Stateful exports on **one** binding: e.g. `let x = instance(); let a = x.increment-and-get(); let b = x.increment-and-get(); a + b` yields `3` if the component is stateful—because **`x`** is a single worker. A **second** `let y = instance();` is a **different** worker (fresh state); use `instance("same-name")` when you need two Rib variables to share one guest.
- Narrow embedding surface with **`rib`** and common error types **re-exported** from `rib-repl` so many projects only add one dependency.
---
diff --git a/rib-repl/src/compiler.rs b/rib-repl/src/compiler.rs
index 501b0ee..120ddeb 100644
--- a/rib-repl/src/compiler.rs
+++ b/rib-repl/src/compiler.rs
@@ -114,15 +114,11 @@ pub fn get_identifiers(inferred_expr: &InferredExpr) -> Vec {
let mut identifiers = Vec::new();
visit_post_order_rev_mut(&mut expr, &mut |expr| match expr {
- Expr::Let { variable_id, .. } => {
- if !identifiers.contains(variable_id) {
- identifiers.push(variable_id.clone());
- }
+ Expr::Let { variable_id, .. } if !identifiers.contains(variable_id) => {
+ identifiers.push(variable_id.clone());
}
- Expr::Identifier { variable_id, .. } => {
- if !identifiers.contains(variable_id) {
- identifiers.push(variable_id.clone());
- }
+ Expr::Identifier { variable_id, .. } if !identifiers.contains(variable_id) => {
+ identifiers.push(variable_id.clone());
}
_ => {}
});
diff --git a/rib-repl/src/invoke.rs b/rib-repl/src/invoke.rs
index 6ad1183..eb22a10 100644
--- a/rib-repl/src/invoke.rs
+++ b/rib-repl/src/invoke.rs
@@ -1,4 +1,5 @@
use crate::repl_state::ReplState;
+use crate::rib_val::RibVal;
use async_trait::async_trait;
use rib::wit_type::WitType;
use rib::ValueAndType;
@@ -6,9 +7,14 @@ use rib::{
ComponentDependencyKey, EvaluatedFnArgs, EvaluatedFqFn, EvaluatedWorkerName, InstructionId,
RibComponentFunctionInvoke, RibFunctionInvokeResult,
};
+use std::convert::TryFrom;
use std::sync::Arc;
use uuid::Uuid;
+fn io_other_box(err: impl std::fmt::Display) -> Box {
+ Box::new(std::io::Error::other(err.to_string()))
+}
+
#[async_trait]
pub trait ComponentFunctionInvoke {
async fn invoke(
@@ -17,9 +23,9 @@ pub trait ComponentFunctionInvoke {
component_name: &str,
worker_name: &str,
function_name: &str,
- args: Vec,
+ args: Vec,
return_type: Option,
- ) -> anyhow::Result